michael@0: /** michael@0: * Copyright (c) 2012, Ben Fortuna michael@0: * All rights reserved. michael@0: * michael@0: * Redistribution and use in source and binary forms, with or without michael@0: * modification, are permitted provided that the following conditions michael@0: * are met: michael@0: * michael@0: * o Redistributions of source code must retain the above copyright michael@0: * notice, this list of conditions and the following disclaimer. michael@0: * michael@0: * o Redistributions in binary form must reproduce the above copyright michael@0: * notice, this list of conditions and the following disclaimer in the michael@0: * documentation and/or other materials provided with the distribution. michael@0: * michael@0: * o Neither the name of Ben Fortuna nor the names of any other contributors michael@0: * may be used to endorse or promote products derived from this software michael@0: * without specific prior written permission. michael@0: * michael@0: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT michael@0: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR michael@0: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR michael@0: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, michael@0: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, michael@0: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR michael@0: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF michael@0: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING michael@0: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS michael@0: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: */ michael@0: package net.fortuna.ical4j.model.component; michael@0: michael@0: import java.io.IOException; michael@0: import java.net.URISyntaxException; michael@0: import java.text.ParseException; michael@0: import java.util.HashMap; michael@0: import java.util.Iterator; michael@0: import java.util.Map; michael@0: michael@0: import net.fortuna.ical4j.model.Component; michael@0: import net.fortuna.ical4j.model.ComponentList; michael@0: import net.fortuna.ical4j.model.Date; michael@0: import net.fortuna.ical4j.model.DateTime; michael@0: import net.fortuna.ical4j.model.Dur; michael@0: import net.fortuna.ical4j.model.Parameter; michael@0: import net.fortuna.ical4j.model.Period; michael@0: import net.fortuna.ical4j.model.PeriodList; michael@0: import net.fortuna.ical4j.model.Property; michael@0: import net.fortuna.ical4j.model.PropertyList; michael@0: import net.fortuna.ical4j.model.ValidationException; michael@0: import net.fortuna.ical4j.model.Validator; michael@0: import net.fortuna.ical4j.model.parameter.Value; michael@0: import net.fortuna.ical4j.model.property.Clazz; michael@0: import net.fortuna.ical4j.model.property.Created; michael@0: import net.fortuna.ical4j.model.property.Description; michael@0: import net.fortuna.ical4j.model.property.DtEnd; michael@0: import net.fortuna.ical4j.model.property.DtStamp; michael@0: import net.fortuna.ical4j.model.property.DtStart; michael@0: import net.fortuna.ical4j.model.property.Duration; michael@0: import net.fortuna.ical4j.model.property.Geo; michael@0: import net.fortuna.ical4j.model.property.LastModified; michael@0: import net.fortuna.ical4j.model.property.Location; michael@0: import net.fortuna.ical4j.model.property.Method; michael@0: import net.fortuna.ical4j.model.property.Organizer; michael@0: import net.fortuna.ical4j.model.property.Priority; michael@0: import net.fortuna.ical4j.model.property.RecurrenceId; michael@0: import net.fortuna.ical4j.model.property.Sequence; michael@0: import net.fortuna.ical4j.model.property.Status; michael@0: import net.fortuna.ical4j.model.property.Summary; michael@0: import net.fortuna.ical4j.model.property.Transp; michael@0: import net.fortuna.ical4j.model.property.Uid; michael@0: import net.fortuna.ical4j.model.property.Url; michael@0: import net.fortuna.ical4j.util.CompatibilityHints; michael@0: import net.fortuna.ical4j.util.ComponentValidator; michael@0: import net.fortuna.ical4j.util.Dates; michael@0: import net.fortuna.ical4j.util.PropertyValidator; michael@0: import net.fortuna.ical4j.util.Strings; michael@0: michael@4: import org.apache.commons.lang3.ObjectUtils; michael@4: import org.apache.commons.lang3.builder.HashCodeBuilder; michael@0: michael@0: /** michael@0: * $Id$ [Apr 5, 2004] michael@0: * michael@0: * Defines an iCalendar VEVENT component. michael@0: * michael@0: *
michael@0: * 4.6.1 Event Component michael@0: * michael@0: * Component Name: "VEVENT" michael@0: * michael@0: * Purpose: Provide a grouping of component properties that describe an michael@0: * event. michael@0: * michael@0: * Format Definition: A "VEVENT" calendar component is defined by the michael@0: * following notation: michael@0: * michael@0: * eventc = "BEGIN" ":" "VEVENT" CRLF michael@0: * eventprop *alarmc michael@0: * "END" ":" "VEVENT" CRLF michael@0: * michael@0: * eventprop = *( michael@0: * michael@0: * ; the following are optional, michael@0: * ; but MUST NOT occur more than once michael@0: * michael@0: * class / created / description / dtstart / geo / michael@0: * last-mod / location / organizer / priority / michael@0: * dtstamp / seq / status / summary / transp / michael@0: * uid / url / recurid / michael@0: * michael@0: * ; either 'dtend' or 'duration' may appear in michael@0: * ; a 'eventprop', but 'dtend' and 'duration' michael@0: * ; MUST NOT occur in the same 'eventprop' michael@0: * michael@0: * dtend / duration / michael@0: * michael@0: * ; the following are optional, michael@0: * ; and MAY occur more than once michael@0: * michael@0: * attach / attendee / categories / comment / michael@0: * contact / exdate / exrule / rstatus / related / michael@0: * resources / rdate / rrule / x-prop michael@0: * michael@0: * ) michael@0: *michael@0: * michael@0: * Example 1 - Creating a new all-day event: michael@0: * michael@0: *
michael@0: * java.util.Calendar cal = java.util.Calendar.getInstance();
michael@0: * cal.set(java.util.Calendar.MONTH, java.util.Calendar.DECEMBER);
michael@0: * cal.set(java.util.Calendar.DAY_OF_MONTH, 25);
michael@0: *
michael@0: * VEvent christmas = new VEvent(cal.getTime(), "Christmas Day");
michael@0: *
michael@0: * // initialise as an all-day event..
michael@0: * christmas.getProperties().getProperty(Property.DTSTART).getParameters().add(
michael@0: * Value.DATE);
michael@0: *
michael@0: * // add timezone information..
michael@0: * VTimeZone tz = VTimeZone.getDefault();
michael@0: * TzId tzParam = new TzId(tz.getProperties().getProperty(Property.TZID)
michael@0: * .getValue());
michael@0: * christmas.getProperties().getProperty(Property.DTSTART).getParameters().add(
michael@0: * tzParam);
michael@0: *
michael@0: *
michael@0: * Example 2 - Creating an event of one (1) hour duration:
michael@0: *
michael@0: *
michael@0: * java.util.Calendar cal = java.util.Calendar.getInstance();
michael@0: * // tomorrow..
michael@0: * cal.add(java.util.Calendar.DAY_OF_MONTH, 1);
michael@0: * cal.set(java.util.Calendar.HOUR_OF_DAY, 9);
michael@0: * cal.set(java.util.Calendar.MINUTE, 30);
michael@0: *
michael@0: * VEvent meeting = new VEvent(cal.getTime(), 1000 * 60 * 60, "Progress Meeting");
michael@0: *
michael@0: * // add timezone information..
michael@0: * VTimeZone tz = VTimeZone.getDefault();
michael@0: * TzId tzParam = new TzId(tz.getProperties().getProperty(Property.TZID)
michael@0: * .getValue());
michael@0: * meeting.getProperties().getProperty(Property.DTSTART).getParameters().add(
michael@0: * tzParam);
michael@0: *
michael@0: *
michael@0: * Example 3 - Retrieve a list of periods representing a recurring event in a specified range:
michael@0: *
michael@0: *
michael@0: * Calendar weekday9AM = Calendar.getInstance();
michael@0: * weekday9AM.set(2005, Calendar.MARCH, 7, 9, 0, 0);
michael@0: * weekday9AM.set(Calendar.MILLISECOND, 0);
michael@0: *
michael@0: * Calendar weekday5PM = Calendar.getInstance();
michael@0: * weekday5PM.set(2005, Calendar.MARCH, 7, 17, 0, 0);
michael@0: * weekday5PM.set(Calendar.MILLISECOND, 0);
michael@0: *
michael@0: * // Do the recurrence until December 31st.
michael@0: * Calendar untilCal = Calendar.getInstance();
michael@0: * untilCal.set(2005, Calendar.DECEMBER, 31);
michael@0: * untilCal.set(Calendar.MILLISECOND, 0);
michael@0: *
michael@0: * // 9:00AM to 5:00PM Rule
michael@0: * Recur recur = new Recur(Recur.WEEKLY, untilCal.getTime());
michael@0: * recur.getDayList().add(WeekDay.MO);
michael@0: * recur.getDayList().add(WeekDay.TU);
michael@0: * recur.getDayList().add(WeekDay.WE);
michael@0: * recur.getDayList().add(WeekDay.TH);
michael@0: * recur.getDayList().add(WeekDay.FR);
michael@0: * recur.setInterval(3);
michael@0: * recur.setWeekStartDay(WeekDay.MO.getDay());
michael@0: * RRule rrule = new RRule(recur);
michael@0: *
michael@0: * Summary summary = new Summary("TEST EVENTS THAT HAPPEN 9-5 MON-FRI");
michael@0: *
michael@0: * weekdayNineToFiveEvents = new VEvent();
michael@0: * weekdayNineToFiveEvents.getProperties().add(rrule);
michael@0: * weekdayNineToFiveEvents.getProperties().add(summary);
michael@0: * weekdayNineToFiveEvents.getProperties().add(new DtStart(weekday9AM.getTime()));
michael@0: * weekdayNineToFiveEvents.getProperties().add(new DtEnd(weekday5PM.getTime()));
michael@0: *
michael@0: * // Test Start 04/01/2005, End One month later.
michael@0: * // Query Calendar Start and End Dates.
michael@0: * Calendar queryStartDate = Calendar.getInstance();
michael@0: * queryStartDate.set(2005, Calendar.APRIL, 1, 14, 47, 0);
michael@0: * queryStartDate.set(Calendar.MILLISECOND, 0);
michael@0: * Calendar queryEndDate = Calendar.getInstance();
michael@0: * queryEndDate.set(2005, Calendar.MAY, 1, 11, 15, 0);
michael@0: * queryEndDate.set(Calendar.MILLISECOND, 0);
michael@0: *
michael@0: * // This range is monday to friday every three weeks, starting from
michael@0: * // March 7th 2005, which means for our query dates we need
michael@0: * // April 18th through to the 22nd.
michael@0: * PeriodList periods = weekdayNineToFiveEvents.getPeriods(queryStartDate
michael@0: * .getTime(), queryEndDate.getTime());
michael@0: *
michael@0: *
michael@0: * @author Ben Fortuna
michael@0: */
michael@0: public class VEvent extends CalendarComponent {
michael@0:
michael@0: private static final long serialVersionUID = 2547948989200697335L;
michael@0:
michael@0: private final Map methodValidators = new HashMap();
michael@0: {
michael@0: methodValidators.put(Method.ADD, new AddValidator());
michael@0: methodValidators.put(Method.CANCEL, new CancelValidator());
michael@0: methodValidators.put(Method.COUNTER, new CounterValidator());
michael@0: methodValidators.put(Method.DECLINE_COUNTER, new DeclineCounterValidator());
michael@0: methodValidators.put(Method.PUBLISH, new PublishValidator());
michael@0: methodValidators.put(Method.REFRESH, new RefreshValidator());
michael@0: methodValidators.put(Method.REPLY, new ReplyValidator());
michael@0: methodValidators.put(Method.REQUEST, new RequestValidator());
michael@0: }
michael@0:
michael@0: private ComponentList alarms;
michael@0:
michael@0: /**
michael@0: * Default constructor.
michael@0: */
michael@0: public VEvent() {
michael@0: super(VEVENT);
michael@0: this.alarms = new ComponentList();
michael@0: getProperties().add(new DtStamp());
michael@0: }
michael@0:
michael@0: /**
michael@0: * Constructor.
michael@0: * @param properties a list of properties
michael@0: */
michael@0: public VEvent(final PropertyList properties) {
michael@0: super(VEVENT, properties);
michael@0: this.alarms = new ComponentList();
michael@0: }
michael@0:
michael@0: /**
michael@0: * Constructor.
michael@0: * @param properties a list of properties
michael@0: * @param alarms a list of alarms
michael@0: */
michael@0: public VEvent(final PropertyList properties, final ComponentList alarms) {
michael@0: super(VEVENT, properties);
michael@0: this.alarms = alarms;
michael@0: }
michael@0:
michael@0: /**
michael@0: * Constructs a new VEVENT instance starting at the specified time with the specified summary.
michael@0: * @param start the start date of the new event
michael@0: * @param summary the event summary
michael@0: */
michael@0: public VEvent(final Date start, final String summary) {
michael@0: this();
michael@0: getProperties().add(new DtStart(start));
michael@0: getProperties().add(new Summary(summary));
michael@0: }
michael@0:
michael@0: /**
michael@0: * Constructs a new VEVENT instance starting and ending at the specified times with the specified summary.
michael@0: * @param start the start date of the new event
michael@0: * @param end the end date of the new event
michael@0: * @param summary the event summary
michael@0: */
michael@0: public VEvent(final Date start, final Date end, final String summary) {
michael@0: this();
michael@0: getProperties().add(new DtStart(start));
michael@0: getProperties().add(new DtEnd(end));
michael@0: getProperties().add(new Summary(summary));
michael@0: }
michael@0:
michael@0: /**
michael@0: * Constructs a new VEVENT instance starting at the specified times, for the specified duration, with the specified
michael@0: * summary.
michael@0: * @param start the start date of the new event
michael@0: * @param duration the duration of the new event
michael@0: * @param summary the event summary
michael@0: */
michael@0: public VEvent(final Date start, final Dur duration, final String summary) {
michael@0: this();
michael@0: getProperties().add(new DtStart(start));
michael@0: getProperties().add(new Duration(duration));
michael@0: getProperties().add(new Summary(summary));
michael@0: }
michael@0:
michael@0: /**
michael@0: * Returns the list of alarms for this event.
michael@0: * @return a component list
michael@0: */
michael@0: public final ComponentList getAlarms() {
michael@0: return alarms;
michael@0: }
michael@0:
michael@0: /**
michael@0: * {@inheritDoc}
michael@0: */
michael@0: public final String toString() {
michael@0: final StringBuffer b = new StringBuffer();
michael@0: b.append(BEGIN);
michael@0: b.append(':');
michael@0: b.append(getName());
michael@0: b.append(Strings.LINE_SEPARATOR);
michael@0: b.append(getProperties());
michael@0: b.append(getAlarms());
michael@0: b.append(END);
michael@0: b.append(':');
michael@0: b.append(getName());
michael@0: b.append(Strings.LINE_SEPARATOR);
michael@0: return b.toString();
michael@0: }
michael@0:
michael@0: /**
michael@0: * {@inheritDoc}
michael@0: */
michael@0: public final void validate(final boolean recurse) throws ValidationException {
michael@0:
michael@0: // validate that getAlarms() only contains VAlarm components
michael@0: final Iterator iterator = getAlarms().iterator();
michael@0: while (iterator.hasNext()) {
michael@0: final Component component = (Component) iterator.next();
michael@0:
michael@0: if (!(component instanceof VAlarm)) {
michael@0: throw new ValidationException("Component ["
michael@0: + component.getName() + "] may not occur in VEVENT");
michael@0: }
michael@0:
michael@0: ((VAlarm) component).validate(recurse);
michael@0: }
michael@0:
michael@0: if (!CompatibilityHints
michael@0: .isHintEnabled(CompatibilityHints.KEY_RELAXED_VALIDATION)) {
michael@0:
michael@0: // From "4.8.4.7 Unique Identifier":
michael@0: // Conformance: The property MUST be specified in the "VEVENT", "VTODO",
michael@0: // "VJOURNAL" or "VFREEBUSY" calendar components.
michael@0: PropertyValidator.getInstance().assertOne(Property.UID,
michael@0: getProperties());
michael@0:
michael@0: // From "4.8.7.2 Date/Time Stamp":
michael@0: // Conformance: This property MUST be included in the "VEVENT", "VTODO",
michael@0: // "VJOURNAL" or "VFREEBUSY" calendar components.
michael@0: PropertyValidator.getInstance().assertOne(Property.DTSTAMP,
michael@0: getProperties());
michael@0: }
michael@0:
michael@0: /*
michael@0: * ; the following are optional, ; but MUST NOT occur more than once class / created / description / dtstart /
michael@0: * geo / last-mod / location / organizer / priority / dtstamp / seq / status / summary / transp / uid / url /
michael@0: * recurid /
michael@0: */
michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.CLASS,
michael@0: getProperties());
michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.CREATED,
michael@0: getProperties());
michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DESCRIPTION,
michael@0: getProperties());
michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DTSTART,
michael@0: getProperties());
michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.GEO,
michael@0: getProperties());
michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.LAST_MODIFIED,
michael@0: getProperties());
michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.LOCATION,
michael@0: getProperties());
michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.ORGANIZER,
michael@0: getProperties());
michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.PRIORITY,
michael@0: getProperties());
michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DTSTAMP,
michael@0: getProperties());
michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.SEQUENCE,
michael@0: getProperties());
michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.STATUS,
michael@0: getProperties());
michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.SUMMARY,
michael@0: getProperties());
michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.TRANSP,
michael@0: getProperties());
michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.UID,
michael@0: getProperties());
michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.URL,
michael@0: getProperties());
michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.RECURRENCE_ID,
michael@0: getProperties());
michael@0:
michael@0: final Status status = (Status) getProperty(Property.STATUS);
michael@0: if (status != null && !Status.VEVENT_TENTATIVE.getValue().equals(status.getValue())
michael@0: && !Status.VEVENT_CONFIRMED.getValue().equals(status.getValue())
michael@0: && !Status.VEVENT_CANCELLED.getValue().equals(status.getValue())) {
michael@0: throw new ValidationException("Status property ["
michael@0: + status.toString() + "] is not applicable for VEVENT");
michael@0: }
michael@0:
michael@0: /*
michael@0: * ; either 'dtend' or 'duration' may appear in ; a 'eventprop', but 'dtend' and 'duration' ; MUST NOT occur in
michael@0: * the same 'eventprop' dtend / duration /
michael@0: */
michael@0: try {
michael@0: PropertyValidator.getInstance().assertNone(Property.DTEND,
michael@0: getProperties());
michael@0: }
michael@0: catch (ValidationException ve) {
michael@0: PropertyValidator.getInstance().assertNone(Property.DURATION,
michael@0: getProperties());
michael@0: }
michael@0:
michael@0: if (getProperty(Property.DTEND) != null) {
michael@0:
michael@0: /*
michael@0: * The "VEVENT" is also the calendar component used to specify an anniversary or daily reminder within a
michael@0: * calendar. These events have a DATE value type for the "DTSTART" property instead of the default data type
michael@0: * of DATE-TIME. If such a "VEVENT" has a "DTEND" property, it MUST be specified as a DATE value also. The
michael@0: * anniversary type of "VEVENT" can span more than one date (i.e, "DTEND" property value is set to a
michael@0: * calendar date after the "DTSTART" property value).
michael@0: */
michael@0: final DtStart start = (DtStart) getProperty(Property.DTSTART);
michael@0: final DtEnd end = (DtEnd) getProperty(Property.DTEND);
michael@0:
michael@0: if (start != null) {
michael@0: final Parameter startValue = start.getParameter(Parameter.VALUE);
michael@0: final Parameter endValue = end.getParameter(Parameter.VALUE);
michael@0:
michael@0: boolean startEndValueMismatch = false;
michael@0: if (endValue != null) {
michael@0: if (startValue != null && !endValue.equals(startValue)) {
michael@0: // invalid..
michael@0: startEndValueMismatch = true;
michael@0: }
michael@0: else if (startValue == null && !Value.DATE_TIME.equals(endValue)) {
michael@0: // invalid..
michael@0: startEndValueMismatch = true;
michael@0: }
michael@0: }
michael@0: else if (startValue != null && !Value.DATE_TIME.equals(startValue)) {
michael@0: //invalid..
michael@0: startEndValueMismatch = true;
michael@0: }
michael@0: if (startEndValueMismatch) {
michael@0: throw new ValidationException("Property [" + Property.DTEND
michael@0: + "] must have the same [" + Parameter.VALUE
michael@0: + "] as [" + Property.DTSTART + "]");
michael@0: }
michael@0: }
michael@0: }
michael@0:
michael@0: /*
michael@0: * ; the following are optional, ; and MAY occur more than once attach / attendee / categories / comment /
michael@0: * contact / exdate / exrule / rstatus / related / resources / rdate / rrule / x-prop
michael@0: */
michael@0:
michael@0: if (recurse) {
michael@0: validateProperties();
michael@0: }
michael@0: }
michael@0:
michael@0: /**
michael@0: * {@inheritDoc}
michael@0: */
michael@0: protected Validator getValidator(Method method) {
michael@0: return (Validator) methodValidators.get(method);
michael@0: }
michael@0:
michael@0: /**
michael@0: * METHOD:ADD Validator.
michael@0: *
michael@0: * michael@0: * Component/Property Presence michael@0: * ------------------- ---------------------------------------------- michael@0: * METHOD 1 MUST be "ADD" michael@0: * VEVENT 1 michael@0: * DTSTAMP 1 michael@0: * DTSTART 1 michael@0: * ORGANIZER 1 michael@0: * SEQUENCE 1 MUST be greater than 0 michael@0: * SUMMARY 1 Can be null michael@0: * UID 1 MUST match that of the original event michael@0: * michael@0: * ATTACH 0+ michael@0: * ATTENDEE 0+ michael@0: * CATEGORIES 0 or 1 This property MAY contain a list of values michael@0: * CLASS 0 or 1 michael@0: * COMMENT 0 or 1 michael@0: * CONTACT 0+ michael@0: * CREATED 0 or 1 michael@0: * DESCRIPTION 0 or 1 Can be null michael@0: * DTEND 0 or 1 if present DURATION MUST NOT be present michael@0: * DURATION 0 or 1 if present DTEND MUST NOT be present michael@0: * EXDATE 0+ michael@0: * EXRULE 0+ michael@0: * GEO 0 or 1 michael@0: * LAST-MODIFIED 0 or 1 michael@0: * LOCATION 0 or 1 michael@0: * PRIORITY 0 or 1 michael@0: * RDATE 0+ michael@0: * RELATED-TO 0+ michael@0: * RESOURCES 0 or 1 This property MAY contain a list of values michael@0: * RRULE 0+ michael@0: * STATUS 0 or 1 MAY be one of TENTATIVE/CONFIRMED michael@0: * TRANSP 0 or 1 michael@0: * URL 0 or 1 michael@0: * X-PROPERTY 0+ michael@0: * michael@0: * RECURRENCE-ID 0 michael@0: * REQUEST-STATUS 0 michael@0: * michael@0: * VALARM 0+ michael@0: * VTIMEZONE 0+ MUST be present if any date/time refers to michael@0: * a timezone michael@0: * X-COMPONENT 0+ michael@0: * michael@0: * VFREEBUSY 0 michael@0: * VTODO 0 michael@0: * VJOURNAL 0 michael@0: *michael@0: * michael@0: */ michael@0: private class AddValidator implements Validator { michael@0: michael@0: private static final long serialVersionUID = 1L; michael@0: michael@0: public void validate() throws ValidationException { michael@0: PropertyValidator.getInstance().assertOne(Property.DTSTAMP, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.DTSTART, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.ORGANIZER, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.SEQUENCE, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.SUMMARY, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.UID, getProperties()); michael@0: michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.CATEGORIES, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.CLASS, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.CREATED, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DESCRIPTION, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DTEND, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DURATION, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.GEO, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.LAST_MODIFIED, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.LOCATION, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.PRIORITY, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.RESOURCES, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.STATUS, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.TRANSP, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.URL, getProperties()); michael@0: michael@0: PropertyValidator.getInstance().assertNone(Property.RECURRENCE_ID, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.REQUEST_STATUS, getProperties()); michael@0: michael@0: for (final Iterator i = getAlarms().iterator(); i.hasNext();) { michael@0: final VAlarm alarm = (VAlarm) i.next(); michael@0: alarm.validate(Method.ADD); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * METHOD:CANCEL Validator. michael@0: * michael@0: *
michael@0: * Component/Property Presence michael@0: * ------------------- ---------------------------------------------- michael@0: * METHOD 1 MUST be "CANCEL" michael@0: * michael@0: * VEVENT 1+ All must have the same UID michael@0: * ATTENDEE 0+ MUST include all "Attendees" being removed michael@0: * the event. MUST include all "Attendees" if michael@0: * the entire event is cancelled. michael@0: * DTSTAMP 1 michael@0: * ORGANIZER 1 michael@0: * SEQUENCE 1 michael@0: * UID 1 MUST be the UID of the original REQUEST michael@0: * michael@0: * COMMENT 0 or 1 michael@0: * ATTACH 0+ michael@0: * CATEGORIES 0 or 1 This property may contain a list of values michael@0: * CLASS 0 or 1 michael@0: * CONTACT 0+ michael@0: * CREATED 0 or 1 michael@0: * DESCRIPTION 0 or 1 michael@0: * DTEND 0 or 1 if present DURATION MUST NOT be present michael@0: * DTSTART 0 or 1 michael@0: * DURATION 0 or 1 if present DTEND MUST NOT be present michael@0: * EXDATE 0+ michael@0: * EXRULE 0+ michael@0: * GEO 0 or 1 michael@0: * LAST-MODIFIED 0 or 1 michael@0: * LOCATION 0 or 1 michael@0: * PRIORITY 0 or 1 michael@0: * RDATE 0+ michael@0: * RECURRENCE-ID 0 or 1 MUST be present if referring to one or michael@0: * more or more recurring instances. michael@0: * Otherwise it MUST NOT be present michael@0: * RELATED-TO 0+ michael@0: * RESOURCES 0 or 1 michael@0: * RRULE 0+ michael@0: * STATUS 0 or 1 MUST be set to CANCELLED. If uninviting michael@0: * specific "Attendees" then MUST NOT be michael@0: * included. michael@0: * SUMMARY 0 or 1 michael@0: * TRANSP 0 or 1 michael@0: * URL 0 or 1 michael@0: * X-PROPERTY 0+ michael@0: * REQUEST-STATUS 0 michael@0: * michael@0: * VTIMEZONE 0+ MUST be present if any date/time refers to michael@0: * a timezone michael@0: * X-COMPONENT 0+ michael@0: * michael@0: * VTODO 0 michael@0: * VJOURNAL 0 michael@0: * VFREEBUSY 0 michael@0: * VALARM 0 michael@0: *michael@0: * michael@0: */ michael@0: private class CancelValidator implements Validator { michael@0: michael@0: private static final long serialVersionUID = 1L; michael@0: michael@0: public final void validate() throws ValidationException { michael@0: PropertyValidator.getInstance().assertOne(Property.DTSTAMP, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.DTSTART, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.ORGANIZER, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.SEQUENCE, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.UID, getProperties()); michael@0: michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.CATEGORIES, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.CLASS, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.CREATED, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DESCRIPTION, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DTEND, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DTSTART, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DURATION, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.GEO, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.LAST_MODIFIED, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.LOCATION, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.PRIORITY, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.RECURRENCE_ID, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.RESOURCES, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.STATUS, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.SUMMARY, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.TRANSP, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.URL, getProperties()); michael@0: michael@0: PropertyValidator.getInstance().assertNone(Property.REQUEST_STATUS, getProperties()); michael@0: michael@0: ComponentValidator.assertNone(Component.VALARM, getAlarms()); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * METHOD:COUNTER Validator. michael@0: * michael@0: *
michael@0: * Component/Property Presence michael@0: * ------------------- ---------------------------------------------- michael@0: * METHOD 1 MUST be "COUNTER" michael@0: * michael@0: * VEVENT 1 michael@0: * DTSTAMP 1 michael@0: * DTSTART 1 michael@0: * ORGANIZER 1 MUST be the "Organizer" of the original michael@0: * event michael@0: * SEQUENCE 1 MUST be present if value is greater than 0, michael@0: * MAY be present if 0 michael@0: * SUMMARY 1 Can be null michael@0: * UID 1 MUST be the UID associated with the REQUEST michael@0: * being countered michael@0: * michael@0: * ATTACH 0+ michael@0: * ATTENDEE 0+ Can also be used to propose other michael@0: * "Attendees" michael@0: * CATEGORIES 0 or 1 This property may contain a list of values michael@0: * CLASS 0 or 1 michael@0: * COMMENT 0 or 1 michael@0: * CONTACT 0+ michael@0: * CREATED 0 or 1 michael@0: * DESCRIPTION 0 or 1 michael@0: * DTEND 0 or 1 if present DURATION MUST NOT be present michael@0: * DURATION 0 or 1 if present DTEND MUST NOT be present michael@0: * EXDATE 0+ michael@0: * EXRULE 0+ michael@0: * GEO 0 or 1 michael@0: * LAST-MODIFIED 0 or 1 michael@0: * LOCATION 0 or 1 michael@0: * PRIORITY 0 or 1 michael@0: * RDATE 0+ michael@0: * RECURRENCE-ID 0 or 1 MUST only if referring to an instance of a michael@0: * recurring calendar component. Otherwise it michael@0: * MUST NOT be present. michael@0: * RELATED-TO 0+ michael@0: * REQUEST-STATUS 0+ michael@0: * RESOURCES 0 or 1 This property may contain a list of values michael@0: * RRULE 0+ michael@0: * STATUS 0 or 1 Value must be one of CONFIRMED/TENATIVE/ michael@0: * CANCELLED michael@0: * TRANSP 0 or 1 michael@0: * URL 0 or 1 michael@0: * X-PROPERTY 0+ michael@0: * michael@0: * VALARM 0+ michael@0: * VTIMEZONE 0+ MUST be present if any date/time refers to michael@0: * a timezone michael@0: * X-COMPONENT 0+ michael@0: * michael@0: * VTODO 0 michael@0: * VJOURNAL 0 michael@0: * VFREEBUSY 0 michael@0: *michael@0: * michael@0: */ michael@0: private class CounterValidator implements Validator { michael@0: michael@0: private static final long serialVersionUID = 1L; michael@0: michael@0: public void validate() throws ValidationException { michael@0: PropertyValidator.getInstance().assertOne(Property.DTSTAMP, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.DTSTART, getProperties()); michael@0: michael@0: if (!CompatibilityHints.isHintEnabled(CompatibilityHints.KEY_RELAXED_VALIDATION)) { michael@0: PropertyValidator.getInstance().assertOne(Property.ORGANIZER, getProperties()); michael@0: } michael@0: michael@0: PropertyValidator.getInstance().assertOne(Property.SEQUENCE, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.SUMMARY, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.UID, getProperties()); michael@0: michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.CATEGORIES, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.CLASS, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.CREATED, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DESCRIPTION, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DTEND, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DURATION, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.GEO, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.LAST_MODIFIED, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.LOCATION, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.PRIORITY, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.RECURRENCE_ID, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.RESOURCES, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.STATUS, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.TRANSP, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.URL, getProperties()); michael@0: michael@0: for (final Iterator i = getAlarms().iterator(); i.hasNext();) { michael@0: final VAlarm alarm = (VAlarm) i.next(); michael@0: alarm.validate(Method.COUNTER); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * METHOD:DECLINECOUNTER Validator. michael@0: * michael@0: *
michael@0: * Component/Property Presence michael@0: * ------------------- ---------------------------------------------- michael@0: * METHOD 1 MUST be "DECLINECOUNTER" michael@0: * michael@0: * VEVENT 1 michael@0: * DTSTAMP 1 michael@0: * ORGANIZER 1 michael@0: * UID 1 MUST, same UID specified in original michael@0: * REQUEST and subsequent COUNTER michael@0: * COMMENT 0 or 1 michael@0: * RECURRENCE-ID 0 or 1 MUST only if referring to an instance of a michael@0: * recurring calendar component. Otherwise it michael@0: * MUST NOT be present. michael@0: * REQUEST-STATUS 0+ michael@0: * SEQUENCE 0 OR 1 MUST be present if value is greater than 0, michael@0: * MAY be present if 0 michael@0: * X-PROPERTY 0+ michael@0: * ATTACH 0 michael@0: * ATTENDEE 0 michael@0: * CATEGORIES 0 michael@0: * CLASS 0 michael@0: * CONTACT 0 michael@0: * CREATED 0 michael@0: * DESCRIPTION 0 michael@0: * DTEND 0 michael@0: * DTSTART 0 michael@0: * DURATION 0 michael@0: * EXDATE 0 michael@0: * EXRULE 0 michael@0: * GEO 0 michael@0: * LAST-MODIFIED 0 michael@0: * LOCATION 0 michael@0: * PRIORITY 0 michael@0: * RDATE 0 michael@0: * RELATED-TO 0 michael@0: * RESOURCES 0 michael@0: * RRULE 0 michael@0: * STATUS 0 michael@0: * SUMMARY 0 michael@0: * TRANSP 0 michael@0: * URL 0 michael@0: * michael@0: * X-COMPONENT 0+ michael@0: * VTODO 0 michael@0: * VJOURNAL 0 michael@0: * VFREEBUSY 0 michael@0: * VTIMEZONE 0 michael@0: * VALARM 0 michael@0: *michael@0: * michael@0: */ michael@0: private class DeclineCounterValidator implements Validator { michael@0: michael@0: private static final long serialVersionUID = 1L; michael@0: michael@0: public void validate() throws ValidationException { michael@0: PropertyValidator.getInstance().assertOne(Property.DTSTAMP, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.ORGANIZER, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.UID, getProperties()); michael@0: michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.RECURRENCE_ID, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.SEQUENCE, getProperties()); michael@0: michael@0: PropertyValidator.getInstance().assertNone(Property.ATTACH, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.ATTENDEE, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.CATEGORIES, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.CLASS, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.CONTACT, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.CREATED, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.DESCRIPTION, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.DTEND, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.DTSTART, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.DURATION, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.EXDATE, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.EXRULE, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.GEO, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.LAST_MODIFIED, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.LOCATION, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.PRIORITY, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.RDATE, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.RELATED_TO, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.RESOURCES, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.RRULE, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.STATUS, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.SUMMARY, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.TRANSP, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.URL, getProperties()); michael@0: michael@0: ComponentValidator.assertNone(Component.VALARM, getAlarms()); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * METHOD:PUBLISH Validator. michael@0: * michael@0: *
michael@0: * Component/Property Presence michael@0: * ------------------- ---------------------------------------------- michael@0: * METHOD 1 MUST equal "PUBLISH" michael@0: * VEVENT 1+ michael@0: * DTSTAMP 1 michael@0: * DTSTART 1 michael@0: * ORGANIZER 1 michael@0: * SUMMARY 1 Can be null. michael@0: * UID 1 michael@0: * RECURRENCE-ID 0 or 1 only if referring to an instance of a michael@0: * recurring calendar component. Otherwise michael@0: * it MUST NOT be present. michael@0: * SEQUENCE 0 or 1 MUST be present if value is greater than michael@0: * 0, MAY be present if 0 michael@0: * ATTACH 0+ michael@0: * CATEGORIES 0 or 1 This property may contain a list of michael@0: * values michael@0: * CLASS 0 or 1 michael@0: * COMMENT 0 or 1 michael@0: * CONTACT 0+ michael@0: * CREATED 0 or 1 michael@0: * DESCRIPTION 0 or 1 Can be null michael@0: * DTEND 0 or 1 if present DURATION MUST NOT be present michael@0: * DURATION 0 or 1 if present DTEND MUST NOT be present michael@0: * EXDATE 0+ michael@0: * EXRULE 0+ michael@0: * GEO 0 or 1 michael@0: * LAST-MODIFIED 0 or 1 michael@0: * LOCATION 0 or 1 michael@0: * PRIORITY 0 or 1 michael@0: * RDATE 0+ michael@0: * RELATED-TO 0+ michael@0: * RESOURCES 0 or 1 This property MAY contain a list of values michael@0: * RRULE 0+ michael@0: * STATUS 0 or 1 MAY be one of TENTATIVE/CONFIRMED/CANCELLED michael@0: * TRANSP 0 or 1 michael@0: * URL 0 or 1 michael@0: * X-PROPERTY 0+ michael@0: * michael@0: * ATTENDEE 0 michael@0: * REQUEST-STATUS 0 michael@0: * michael@0: * VALARM 0+ michael@0: * VFREEBUSY 0 michael@0: * VJOURNAL 0 michael@0: * VTODO 0 michael@0: * VTIMEZONE 0+ MUST be present if any date/time refers to michael@0: * a timezone michael@0: * X-COMPONENT 0+ michael@0: *michael@0: * michael@0: */ michael@0: private class PublishValidator implements Validator { michael@0: michael@0: private static final long serialVersionUID = 1L; michael@0: michael@0: public void validate() throws ValidationException { michael@0: PropertyValidator.getInstance().assertOne(Property.DTSTAMP, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.DTSTART, getProperties()); michael@0: michael@0: if (!CompatibilityHints.isHintEnabled(CompatibilityHints.KEY_RELAXED_VALIDATION)) { michael@0: PropertyValidator.getInstance().assertOne(Property.ORGANIZER, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.SUMMARY, getProperties()); michael@0: } michael@0: michael@0: PropertyValidator.getInstance().assertOne(Property.UID, getProperties()); michael@0: michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.RECURRENCE_ID, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.SEQUENCE, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.CATEGORIES, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.CLASS, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.CREATED, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DESCRIPTION, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DTEND, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DURATION, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.GEO, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.LAST_MODIFIED, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.LOCATION, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.PRIORITY, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.RESOURCES, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.STATUS, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.TRANSP, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.URL, getProperties()); michael@0: michael@0: if (!CompatibilityHints.isHintEnabled(CompatibilityHints.KEY_RELAXED_VALIDATION)) { michael@0: PropertyValidator.getInstance().assertNone(Property.ATTENDEE, getProperties()); michael@0: } michael@0: michael@0: PropertyValidator.getInstance().assertNone(Property.REQUEST_STATUS, getProperties()); michael@0: michael@0: for (final Iterator i = getAlarms().iterator(); i.hasNext();) { michael@0: final VAlarm alarm = (VAlarm) i.next(); michael@0: alarm.validate(Method.PUBLISH); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * METHOD:REFRESH Validator. michael@0: * michael@0: *
michael@0: * Component/Property Presence michael@0: * ------------------- ---------------------------------------------- michael@0: * METHOD 1 MUST be "REFRESH" michael@0: * michael@0: * VEVENT 1 michael@0: * ATTENDEE 1 MUST be the address of requestor michael@0: * DTSTAMP 1 michael@0: * ORGANIZER 1 michael@0: * UID 1 MUST be the UID associated with original michael@0: * REQUEST michael@0: * COMMENT 0 or 1 michael@0: * RECURRENCE-ID 0 or 1 MUST only if referring to an instance of a michael@0: * recurring calendar component. Otherwise michael@0: * it must NOT be present. michael@0: * X-PROPERTY 0+ michael@0: * michael@0: * ATTACH 0 michael@0: * CATEGORIES 0 michael@0: * CLASS 0 michael@0: * CONTACT 0 michael@0: * CREATED 0 michael@0: * DESCRIPTION 0 michael@0: * DTEND 0 michael@0: * DTSTART 0 michael@0: * DURATION 0 michael@0: * EXDATE 0 michael@0: * EXRULE 0 michael@0: * GEO 0 michael@0: * LAST-MODIFIED 0 michael@0: * LOCATION 0 michael@0: * PRIORITY 0 michael@0: * RDATE 0 michael@0: * RELATED-TO 0 michael@0: * REQUEST-STATUS 0 michael@0: * RESOURCES 0 michael@0: * RRULE 0 michael@0: * SEQUENCE 0 michael@0: * STATUS 0 michael@0: * SUMMARY 0 michael@0: * TRANSP 0 michael@0: * URL 0 michael@0: * michael@0: * X-COMPONENT 0+ michael@0: * michael@0: * VTODO 0 michael@0: * VJOURNAL 0 michael@0: * VFREEBUSY 0 michael@0: * VTIMEZONE 0 michael@0: * VALARM 0 michael@0: *michael@0: * michael@0: */ michael@0: private class RefreshValidator implements Validator { michael@0: michael@0: private static final long serialVersionUID = 1L; michael@0: michael@0: public void validate() throws ValidationException { michael@0: PropertyValidator.getInstance().assertOne(Property.ATTENDEE, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.DTSTAMP, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.ORGANIZER, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.UID, getProperties()); michael@0: michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.RECURRENCE_ID, getProperties()); michael@0: michael@0: PropertyValidator.getInstance().assertNone(Property.ATTACH, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.CATEGORIES, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.CLASS, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.CONTACT, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.CREATED, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.DESCRIPTION, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.DTEND, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.DTSTART, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.DURATION, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.EXDATE, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.EXRULE, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.GEO, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.LAST_MODIFIED, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.LOCATION, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.PRIORITY, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.RDATE, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.RELATED_TO, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.REQUEST_STATUS, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.RESOURCES, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.RRULE, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.SEQUENCE, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.STATUS, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.SUMMARY, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.TRANSP, getProperties()); michael@0: PropertyValidator.getInstance().assertNone(Property.URL, getProperties()); michael@0: michael@0: ComponentValidator.assertNone(Component.VALARM, getAlarms()); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * METHOD:REPLY Validator. michael@0: * michael@0: *
michael@0: * Component/Property Presence michael@0: * ------------------- ---------------------------------------------- michael@0: * METHOD 1 MUST be "REPLY" michael@0: * VEVENT 1+ All components MUST have the same UID michael@0: * ATTENDEE 1 MUST be the address of the Attendee michael@0: * replying. michael@0: * DTSTAMP 1 michael@0: * ORGANIZER 1 michael@0: * RECURRENCE-ID 0 or 1 only if referring to an instance of a michael@0: * recurring calendar component. Otherwise michael@0: * it must NOT be present. michael@0: * UID 1 MUST be the UID of the original REQUEST michael@0: * michael@0: * SEQUENCE 0 or 1 MUST if non-zero, MUST be the sequence michael@0: * number of the original REQUEST. MAY be michael@0: * present if 0. michael@0: * michael@0: * ATTACH 0+ michael@0: * CATEGORIES 0 or 1 This property may contain a list of values michael@0: * CLASS 0 or 1 michael@0: * COMMENT 0 or 1 michael@0: * CONTACT 0+ michael@0: * CREATED 0 or 1 michael@0: * DESCRIPTION 0 or 1 michael@0: * DTEND 0 or 1 if present DURATION MUST NOT be present michael@0: * DTSTART 0 or 1 michael@0: * DURATION 0 or 1 if present DTEND MUST NOT be present michael@0: * EXDATE 0+ michael@0: * EXRULE 0+ michael@0: * GEO 0 or 1 michael@0: * LAST-MODIFIED 0 or 1 michael@0: * LOCATION 0 or 1 michael@0: * PRIORITY 0 or 1 michael@0: * RDATE 0+ michael@0: * RELATED-TO 0+ michael@0: * RESOURCES 0 or 1 This property MAY contain a list of values michael@0: * REQUEST-STATUS 0+ michael@0: * RRULE 0+ michael@0: * STATUS 0 or 1 michael@0: * SUMMARY 0 or 1 michael@0: * TRANSP 0 or 1 michael@0: * URL 0 or 1 michael@0: * X-PROPERTY 0+ michael@0: * michael@0: * VTIMEZONE 0 or 1 MUST be present if any date/time refers michael@0: * to a timezone michael@0: * X-COMPONENT 0+ michael@0: * michael@0: * VALARM 0 michael@0: * VFREEBUSY 0 michael@0: * VJOURNAL 0 michael@0: * VTODO 0 michael@0: *michael@0: * michael@0: */ michael@0: private class ReplyValidator implements Validator { michael@0: michael@0: private static final long serialVersionUID = 1L; michael@0: michael@0: public void validate() throws ValidationException { michael@0: PropertyValidator.getInstance().assertOne(Property.ATTENDEE, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.DTSTAMP, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.ORGANIZER, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.UID, getProperties()); michael@0: michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.RECURRENCE_ID, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.SEQUENCE, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.CATEGORIES, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.CLASS, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.CREATED, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DESCRIPTION, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DTEND, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DTSTART, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DURATION, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.GEO, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.LAST_MODIFIED, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.LOCATION, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.PRIORITY, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.RESOURCES, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.STATUS, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.SUMMARY, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.TRANSP, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.URL, getProperties()); michael@0: michael@0: ComponentValidator.assertNone(Component.VALARM, getAlarms()); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * METHOD:REQUEST Validator. michael@0: * michael@0: *
michael@0: * Component/Property Presence michael@0: * ----------------------------------------------------------------- michael@0: * METHOD 1 MUST be "REQUEST" michael@0: * VEVENT 1+ All components MUST have the same UID michael@0: * ATTENDEE 1+ michael@0: * DTSTAMP 1 michael@0: * DTSTART 1 michael@0: * ORGANIZER 1 michael@0: * SEQUENCE 0 or 1 MUST be present if value is greater than 0, michael@0: * MAY be present if 0 michael@0: * SUMMARY 1 Can be null michael@0: * UID 1 michael@0: * michael@0: * ATTACH 0+ michael@0: * CATEGORIES 0 or 1 This property may contain a list of values michael@0: * CLASS 0 or 1 michael@0: * COMMENT 0 or 1 michael@0: * CONTACT 0+ michael@0: * CREATED 0 or 1 michael@0: * DESCRIPTION 0 or 1 Can be null michael@0: * DTEND 0 or 1 if present DURATION MUST NOT be present michael@0: * DURATION 0 or 1 if present DTEND MUST NOT be present michael@0: * EXDATE 0+ michael@0: * EXRULE 0+ michael@0: * GEO 0 or 1 michael@0: * LAST-MODIFIED 0 or 1 michael@0: * LOCATION 0 or 1 michael@0: * PRIORITY 0 or 1 michael@0: * RDATE 0+ michael@0: * RECURRENCE-ID 0 or 1 only if referring to an instance of a michael@0: * recurring calendar component. Otherwise it michael@0: * MUST NOT be present. michael@0: * RELATED-TO 0+ michael@0: * REQUEST-STATUS 0+ michael@0: * RESOURCES 0 or 1 This property MAY contain a list of values michael@0: * RRULE 0+ michael@0: * STATUS 0 or 1 MAY be one of TENTATIVE/CONFIRMED michael@0: * TRANSP 0 or 1 michael@0: * URL 0 or 1 michael@0: * X-PROPERTY 0+ michael@0: * michael@0: * VALARM 0+ michael@0: * VTIMEZONE 0+ MUST be present if any date/time refers to michael@0: * a timezone michael@0: * X-COMPONENT 0+ michael@0: * VFREEBUSY 0 michael@0: * VJOURNAL 0 michael@0: * VTODO 0 michael@0: *michael@0: * michael@0: */ michael@0: private class RequestValidator implements Validator { michael@0: michael@0: private static final long serialVersionUID = 1L; michael@0: michael@0: public void validate() throws ValidationException { michael@0: if (!CompatibilityHints.isHintEnabled(CompatibilityHints.KEY_RELAXED_VALIDATION)) { michael@0: PropertyValidator.getInstance().assertOneOrMore(Property.ATTENDEE, getProperties()); michael@0: } michael@0: michael@0: PropertyValidator.getInstance().assertOne(Property.DTSTAMP, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.DTSTART, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.ORGANIZER, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.SUMMARY, getProperties()); michael@0: PropertyValidator.getInstance().assertOne(Property.UID, getProperties()); michael@0: michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.SEQUENCE, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.CATEGORIES, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.CLASS, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.CREATED, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DESCRIPTION, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DTEND, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.DURATION, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.GEO, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.LAST_MODIFIED, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.LOCATION, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.PRIORITY, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.RECURRENCE_ID, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.RESOURCES, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.STATUS, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.TRANSP, getProperties()); michael@0: PropertyValidator.getInstance().assertOneOrLess(Property.URL, getProperties()); michael@0: michael@0: for (final Iterator i = getAlarms().iterator(); i.hasNext();) { michael@0: final VAlarm alarm = (VAlarm) i.next(); michael@0: alarm.validate(Method.REQUEST); michael@0: } michael@0: } michael@0: } michael@0: /** michael@0: * Returns a normalised list of periods representing the consumed time for this event. michael@0: * @param rangeStart the start of a range michael@0: * @param rangeEnd the end of a range michael@0: * @return a normalised list of periods representing consumed time for this event michael@0: * @see VEvent#getConsumedTime(Date, Date, boolean) michael@0: */ michael@0: public final PeriodList getConsumedTime(final Date rangeStart, michael@0: final Date rangeEnd) { michael@0: return getConsumedTime(rangeStart, rangeEnd, true); michael@0: } michael@0: michael@0: /** michael@0: * Returns a list of periods representing the consumed time for this event in the specified range. Note that the michael@0: * returned list may contain a single period for non-recurring components or multiple periods for recurring michael@0: * components. If no time is consumed by this event an empty list is returned. michael@0: * @param rangeStart the start of the range to check for consumed time michael@0: * @param rangeEnd the end of the range to check for consumed time michael@0: * @param normalise indicate whether the returned list of periods should be normalised michael@0: * @return a list of periods representing consumed time for this event michael@0: */ michael@0: public final PeriodList getConsumedTime(final Date rangeStart, michael@0: final Date rangeEnd, final boolean normalise) { michael@0: PeriodList periods = new PeriodList(); michael@0: // if component is transparent return empty list.. michael@0: if (!Transp.TRANSPARENT.equals(getProperty(Property.TRANSP))) { michael@0: michael@0: // try { michael@0: periods = calculateRecurrenceSet(new Period(new DateTime(rangeStart), michael@0: new DateTime(rangeEnd))); michael@0: // } michael@0: // catch (ValidationException ve) { michael@0: // log.error("Invalid event data", ve); michael@0: // return periods; michael@0: // } michael@0: michael@0: // if periods already specified through recurrence, return.. michael@0: // ..also normalise before returning. michael@0: if (!periods.isEmpty() && normalise) { michael@0: periods = periods.normalise(); michael@0: } michael@0: } michael@0: michael@0: return periods; michael@0: } michael@0: michael@0: /** michael@0: * Returns a single occurrence of a recurring event. michael@0: * @param date a date on which the occurence should occur michael@0: * @return a single non-recurring event instance for the specified date, or null if the event doesn't michael@0: * occur on the specified date michael@0: * @throws IOException where an error occurs reading data michael@0: * @throws URISyntaxException where an invalid URI is encountered michael@0: * @throws ParseException where an error occurs parsing data michael@0: */ michael@0: public final VEvent getOccurrence(final Date date) throws IOException, michael@0: URISyntaxException, ParseException { michael@0: michael@0: final PeriodList consumedTime = getConsumedTime(date, date); michael@0: for (final Iterator i = consumedTime.iterator(); i.hasNext();) { michael@0: final Period p = (Period) i.next(); michael@0: if (p.getStart().equals(date)) { michael@0: final VEvent occurrence = (VEvent) this.copy(); michael@0: occurrence.getProperties().add(new RecurrenceId(date)); michael@0: return occurrence; michael@0: } michael@0: } michael@0: return null; michael@0: } michael@0: michael@0: /** michael@0: * @return the optional access classification property for an event michael@0: */ michael@0: public final Clazz getClassification() { michael@0: return (Clazz) getProperty(Property.CLASS); michael@0: } michael@0: michael@0: /** michael@0: * @return the optional creation-time property for an event michael@0: */ michael@0: public final Created getCreated() { michael@0: return (Created) getProperty(Property.CREATED); michael@0: } michael@0: michael@0: /** michael@0: * @return the optional description property for an event michael@0: */ michael@0: public final Description getDescription() { michael@0: return (Description) getProperty(Property.DESCRIPTION); michael@0: } michael@0: michael@0: /** michael@0: * Convenience method to pull the DTSTART out of the property list. michael@0: * @return The DtStart object representation of the start Date michael@0: */ michael@0: public final DtStart getStartDate() { michael@0: return (DtStart) getProperty(Property.DTSTART); michael@0: } michael@0: michael@0: /** michael@0: * @return the optional geographic position property for an event michael@0: */ michael@0: public final Geo getGeographicPos() { michael@0: return (Geo) getProperty(Property.GEO); michael@0: } michael@0: michael@0: /** michael@0: * @return the optional last-modified property for an event michael@0: */ michael@0: public final LastModified getLastModified() { michael@0: return (LastModified) getProperty(Property.LAST_MODIFIED); michael@0: } michael@0: michael@0: /** michael@0: * @return the optional location property for an event michael@0: */ michael@0: public final Location getLocation() { michael@0: return (Location) getProperty(Property.LOCATION); michael@0: } michael@0: michael@0: /** michael@0: * @return the optional organizer property for an event michael@0: */ michael@0: public final Organizer getOrganizer() { michael@0: return (Organizer) getProperty(Property.ORGANIZER); michael@0: } michael@0: michael@0: /** michael@0: * @return the optional priority property for an event michael@0: */ michael@0: public final Priority getPriority() { michael@0: return (Priority) getProperty(Property.PRIORITY); michael@0: } michael@0: michael@0: /** michael@0: * @return the optional date-stamp property michael@0: */ michael@0: public final DtStamp getDateStamp() { michael@0: return (DtStamp) getProperty(Property.DTSTAMP); michael@0: } michael@0: michael@0: /** michael@0: * @return the optional sequence number property for an event michael@0: */ michael@0: public final Sequence getSequence() { michael@0: return (Sequence) getProperty(Property.SEQUENCE); michael@0: } michael@0: michael@0: /** michael@0: * @return the optional status property for an event michael@0: */ michael@0: public final Status getStatus() { michael@0: return (Status) getProperty(Property.STATUS); michael@0: } michael@0: michael@0: /** michael@0: * @return the optional summary property for an event michael@0: */ michael@0: public final Summary getSummary() { michael@0: return (Summary) getProperty(Property.SUMMARY); michael@0: } michael@0: michael@0: /** michael@0: * @return the optional time transparency property for an event michael@0: */ michael@0: public final Transp getTransparency() { michael@0: return (Transp) getProperty(Property.TRANSP); michael@0: } michael@0: michael@0: /** michael@0: * @return the optional URL property for an event michael@0: */ michael@0: public final Url getUrl() { michael@0: return (Url) getProperty(Property.URL); michael@0: } michael@0: michael@0: /** michael@0: * @return the optional recurrence identifier property for an event michael@0: */ michael@0: public final RecurrenceId getRecurrenceId() { michael@0: return (RecurrenceId) getProperty(Property.RECURRENCE_ID); michael@0: } michael@0: michael@0: /** michael@0: * Returns the end date of this event. Where an end date is not available it will be derived from the event michael@0: * duration. michael@0: * @return a DtEnd instance, or null if one cannot be derived michael@0: */ michael@0: public final DtEnd getEndDate() { michael@0: return getEndDate(true); michael@0: } michael@0: michael@0: /** michael@0: * Convenience method to pull the DTEND out of the property list. If DTEND was not specified, use the DTSTART + michael@0: * DURATION to calculate it. michael@0: * @param deriveFromDuration specifies whether to derive an end date from the event duration where an end date is michael@0: * not found michael@0: * @return The end for this VEVENT. michael@0: */ michael@0: public final DtEnd getEndDate(final boolean deriveFromDuration) { michael@0: DtEnd dtEnd = (DtEnd) getProperty(Property.DTEND); michael@0: // No DTEND? No problem, we'll use the DURATION. michael@3: if (dtEnd == null && deriveFromDuration && getStartDate() != null) { michael@0: final DtStart dtStart = getStartDate(); michael@3: final Duration vEventDuration; michael@3: if (getDuration() != null) { michael@3: vEventDuration = getDuration(); michael@3: } else if (dtStart.getDate() instanceof DateTime) { michael@3: // If "DTSTART" is a DATE-TIME, then the event's duration is zero (see: RFC 5545, 3.6.1 Event Component) michael@3: vEventDuration = new Duration(new Dur(0, 0, 0, 0)); michael@3: } else { michael@3: // If "DTSTART" is a DATE, then the event's duration is one day (see: RFC 5545, 3.6.1 Event Component) michael@3: vEventDuration = new Duration(new Dur(1, 0, 0, 0)); michael@3: } michael@3: michael@0: dtEnd = new DtEnd(Dates.getInstance(vEventDuration.getDuration() michael@0: .getTime(dtStart.getDate()), (Value) dtStart michael@0: .getParameter(Parameter.VALUE))); michael@0: if (dtStart.isUtc()) { michael@0: dtEnd.setUtc(true); michael@0: } michael@0: } michael@0: return dtEnd; michael@0: } michael@0: michael@0: /** michael@0: * @return the optional Duration property michael@0: */ michael@0: public final Duration getDuration() { michael@0: return (Duration) getProperty(Property.DURATION); michael@0: } michael@0: michael@0: /** michael@0: * Returns the UID property of this component if available. michael@0: * @return a Uid instance, or null if no UID property exists michael@0: */ michael@0: public final Uid getUid() { michael@0: return (Uid) getProperty(Property.UID); michael@0: } michael@0: michael@0: /** michael@0: * {@inheritDoc} michael@0: */ michael@0: public boolean equals(final Object arg0) { michael@0: if (arg0 instanceof VEvent) { michael@0: return super.equals(arg0) michael@0: && ObjectUtils.equals(alarms, ((VEvent) arg0).getAlarms()); michael@0: } michael@0: return super.equals(arg0); michael@0: } michael@0: michael@0: /** michael@0: * {@inheritDoc} michael@0: */ michael@0: public int hashCode() { michael@0: return new HashCodeBuilder().append(getName()).append(getProperties()) michael@0: .append(getAlarms()).toHashCode(); michael@0: } michael@0: michael@0: /** michael@0: * Overrides default copy method to add support for copying alarm sub-components. michael@0: * @return a copy of the instance michael@0: * @throws ParseException where values in the instance cannot be parsed michael@0: * @throws IOException where values in the instance cannot be read michael@0: * @throws URISyntaxException where an invalid URI value is encountered in the instance michael@0: * @see net.fortuna.ical4j.model.Component#copy() michael@0: */ michael@0: public Component copy() throws ParseException, IOException, michael@0: URISyntaxException { michael@0: final VEvent copy = (VEvent) super.copy(); michael@0: copy.alarms = new ComponentList(alarms); michael@0: return copy; michael@0: } michael@0: }