1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/net/fortuna/ical4j/model/component/VFreeBusy.java Tue Feb 10 18:12:00 2015 +0100 1.3 @@ -0,0 +1,809 @@ 1.4 +/** 1.5 + * Copyright (c) 2012, Ben Fortuna 1.6 + * All rights reserved. 1.7 + * 1.8 + * Redistribution and use in source and binary forms, with or without 1.9 + * modification, are permitted provided that the following conditions 1.10 + * are met: 1.11 + * 1.12 + * o Redistributions of source code must retain the above copyright 1.13 + * notice, this list of conditions and the following disclaimer. 1.14 + * 1.15 + * o Redistributions in binary form must reproduce the above copyright 1.16 + * notice, this list of conditions and the following disclaimer in the 1.17 + * documentation and/or other materials provided with the distribution. 1.18 + * 1.19 + * o Neither the name of Ben Fortuna nor the names of any other contributors 1.20 + * may be used to endorse or promote products derived from this software 1.21 + * without specific prior written permission. 1.22 + * 1.23 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 1.24 + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 1.25 + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 1.26 + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 1.27 + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 1.28 + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 1.29 + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 1.30 + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 1.31 + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 1.32 + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 1.33 + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 1.34 + */ 1.35 +package net.fortuna.ical4j.model.component; 1.36 + 1.37 +import java.util.HashMap; 1.38 +import java.util.Iterator; 1.39 +import java.util.Map; 1.40 + 1.41 +import net.fortuna.ical4j.model.Component; 1.42 +import net.fortuna.ical4j.model.ComponentList; 1.43 +import net.fortuna.ical4j.model.DateRange; 1.44 +import net.fortuna.ical4j.model.DateTime; 1.45 +import net.fortuna.ical4j.model.Dur; 1.46 +import net.fortuna.ical4j.model.Period; 1.47 +import net.fortuna.ical4j.model.PeriodList; 1.48 +import net.fortuna.ical4j.model.Property; 1.49 +import net.fortuna.ical4j.model.PropertyList; 1.50 +import net.fortuna.ical4j.model.ValidationException; 1.51 +import net.fortuna.ical4j.model.Validator; 1.52 +import net.fortuna.ical4j.model.parameter.FbType; 1.53 +import net.fortuna.ical4j.model.property.Contact; 1.54 +import net.fortuna.ical4j.model.property.DtEnd; 1.55 +import net.fortuna.ical4j.model.property.DtStamp; 1.56 +import net.fortuna.ical4j.model.property.DtStart; 1.57 +import net.fortuna.ical4j.model.property.Duration; 1.58 +import net.fortuna.ical4j.model.property.FreeBusy; 1.59 +import net.fortuna.ical4j.model.property.Method; 1.60 +import net.fortuna.ical4j.model.property.Organizer; 1.61 +import net.fortuna.ical4j.model.property.Uid; 1.62 +import net.fortuna.ical4j.model.property.Url; 1.63 +import net.fortuna.ical4j.util.CompatibilityHints; 1.64 +import net.fortuna.ical4j.util.PropertyValidator; 1.65 + 1.66 +/** 1.67 + * $Id$ [Apr 5, 2004] 1.68 + * 1.69 + * Defines an iCalendar VFREEBUSY component. 1.70 + * 1.71 + * <pre> 1.72 + * 4.6.4 Free/Busy Component 1.73 + * 1.74 + * Component Name: VFREEBUSY 1.75 + * 1.76 + * Purpose: Provide a grouping of component properties that describe 1.77 + * either a request for free/busy time, describe a response to a request 1.78 + * for free/busy time or describe a published set of busy time. 1.79 + * 1.80 + * Formal Definition: A "VFREEBUSY" calendar component is defined by the 1.81 + * following notation: 1.82 + * 1.83 + * freebusyc = "BEGIN" ":" "VFREEBUSY" CRLF 1.84 + * fbprop 1.85 + * "END" ":" "VFREEBUSY" CRLF 1.86 + * 1.87 + * fbprop = *( 1.88 + * 1.89 + * ; the following are optional, 1.90 + * ; but MUST NOT occur more than once 1.91 + * 1.92 + * contact / dtstart / dtend / duration / dtstamp / 1.93 + * organizer / uid / url / 1.94 + * 1.95 + * ; the following are optional, 1.96 + * ; and MAY occur more than once 1.97 + * 1.98 + * attendee / comment / freebusy / rstatus / x-prop 1.99 + * 1.100 + * ) 1.101 + * 1.102 + * Description: A "VFREEBUSY" calendar component is a grouping of 1.103 + * component properties that represents either a request for, a reply to 1.104 + * a request for free or busy time information or a published set of 1.105 + * busy time information. 1.106 + * 1.107 + * When used to request free/busy time information, the "ATTENDEE" 1.108 + * property specifies the calendar users whose free/busy time is being 1.109 + * requested; the "ORGANIZER" property specifies the calendar user who 1.110 + * is requesting the free/busy time; the "DTSTART" and "DTEND" 1.111 + * properties specify the window of time for which the free/busy time is 1.112 + * being requested; the "UID" and "DTSTAMP" properties are specified to 1.113 + * assist in proper sequencing of multiple free/busy time requests. 1.114 + * 1.115 + * When used to reply to a request for free/busy time, the "ATTENDEE" 1.116 + * property specifies the calendar user responding to the free/busy time 1.117 + * request; the "ORGANIZER" property specifies the calendar user that 1.118 + * originally requested the free/busy time; the "FREEBUSY" property 1.119 + * specifies the free/busy time information (if it exists); and the 1.120 + * "UID" and "DTSTAMP" properties are specified to assist in proper 1.121 + * sequencing of multiple free/busy time replies. 1.122 + * 1.123 + * When used to publish busy time, the "ORGANIZER" property specifies 1.124 + * the calendar user associated with the published busy time; the 1.125 + * "DTSTART" and "DTEND" properties specify an inclusive time window 1.126 + * that surrounds the busy time information; the "FREEBUSY" property 1.127 + * specifies the published busy time information; and the "DTSTAMP" 1.128 + * property specifies the date/time that iCalendar object was created. 1.129 + * 1.130 + * The "VFREEBUSY" calendar component cannot be nested within another 1.131 + * calendar component. Multiple "VFREEBUSY" calendar components can be 1.132 + * specified within an iCalendar object. This permits the grouping of 1.133 + * Free/Busy information into logical collections, such as monthly 1.134 + * groups of busy time information. 1.135 + * 1.136 + * The "VFREEBUSY" calendar component is intended for use in iCalendar 1.137 + * object methods involving requests for free time, requests for busy 1.138 + * time, requests for both free and busy, and the associated replies. 1.139 + * 1.140 + * Free/Busy information is represented with the "FREEBUSY" property. 1.141 + * This property provides a terse representation of time periods. One or 1.142 + * more "FREEBUSY" properties can be specified in the "VFREEBUSY" 1.143 + * calendar component. 1.144 + * 1.145 + * When present in a "VFREEBUSY" calendar component, the "DTSTART" and 1.146 + * "DTEND" properties SHOULD be specified prior to any "FREEBUSY" 1.147 + * properties. In a free time request, these properties can be used in 1.148 + * combination with the "DURATION" property to represent a request for a 1.149 + * duration of free time within a specified window of time. 1.150 + * 1.151 + * The recurrence properties ("RRULE", "EXRULE", "RDATE", "EXDATE") are 1.152 + * not permitted within a "VFREEBUSY" calendar component. Any recurring 1.153 + * events are resolved into their individual busy time periods using the 1.154 + * "FREEBUSY" property. 1.155 + * 1.156 + * Example: The following is an example of a "VFREEBUSY" calendar 1.157 + * component used to request free or busy time information: 1.158 + * 1.159 + * BEGIN:VFREEBUSY 1.160 + * ORGANIZER:MAILTO:jane_doe@host1.com 1.161 + * ATTENDEE:MAILTO:john_public@host2.com 1.162 + * DTSTART:19971015T050000Z 1.163 + * DTEND:19971016T050000Z 1.164 + * DTSTAMP:19970901T083000Z 1.165 + * END:VFREEBUSY 1.166 + * 1.167 + * The following is an example of a "VFREEBUSY" calendar component used 1.168 + * to reply to the request with busy time information: 1.169 + * 1.170 + * BEGIN:VFREEBUSY 1.171 + * ORGANIZER:MAILTO:jane_doe@host1.com 1.172 + * ATTENDEE:MAILTO:john_public@host2.com 1.173 + * DTSTAMP:19970901T100000Z 1.174 + * FREEBUSY;VALUE=PERIOD:19971015T050000Z/PT8H30M, 1.175 + * 19971015T160000Z/PT5H30M,19971015T223000Z/PT6H30M 1.176 + * URL:http://host2.com/pub/busy/jpublic-01.ifb 1.177 + * COMMENT:This iCalendar file contains busy time information for 1.178 + * the next three months. 1.179 + * END:VFREEBUSY 1.180 + * 1.181 + * The following is an example of a "VFREEBUSY" calendar component used 1.182 + * to publish busy time information. 1.183 + * 1.184 + * BEGIN:VFREEBUSY 1.185 + * ORGANIZER:jsmith@host.com 1.186 + * DTSTART:19980313T141711Z 1.187 + * DTEND:19980410T141711Z 1.188 + * FREEBUSY:19980314T233000Z/19980315T003000Z 1.189 + * FREEBUSY:19980316T153000Z/19980316T163000Z 1.190 + * FREEBUSY:19980318T030000Z/19980318T040000Z 1.191 + * URL:http://www.host.com/calendar/busytime/jsmith.ifb 1.192 + * END:VFREEBUSY 1.193 + * </pre> 1.194 + * 1.195 + * Example 1 - Requesting all busy time slots for a given period: 1.196 + * 1.197 + * <pre><code> 1.198 + * // request all busy times between today and 1 week from now.. 1.199 + * DateTime start = new DateTime(); 1.200 + * DateTime end = new DateTime(start.getTime() + 1000 * 60 * 60 * 24 * 7); 1.201 + * 1.202 + * VFreeBusy request = new VFreeBusy(start, end); 1.203 + * 1.204 + * VFreeBusy reply = new VFreeBusy(request, calendar.getComponents()); 1.205 + * </code></pre> 1.206 + * 1.207 + * Example 2 - Requesting all free time slots for a given period of at least the specified duration: 1.208 + * 1.209 + * <pre><code> 1.210 + * // request all free time between today and 1 week from now of 1.211 + * // duration 2 hours or more.. 1.212 + * DateTime start = new DateTime(); 1.213 + * DateTime end = new DateTime(start.getTime() + 1000 * 60 * 60 * 24 * 7); 1.214 + * 1.215 + * VFreeBusy request = new VFreeBusy(start, end, new Dur(0, 2, 0, 0)); 1.216 + * 1.217 + * VFreeBusy response = new VFreeBusy(request, myCalendar.getComponents()); 1.218 + * </code></pre> 1.219 + * 1.220 + * @author Ben Fortuna 1.221 + */ 1.222 +public class VFreeBusy extends CalendarComponent { 1.223 + 1.224 + private static final long serialVersionUID = 1046534053331139832L; 1.225 + 1.226 + private final Map methodValidators = new HashMap(); 1.227 + { 1.228 + methodValidators.put(Method.PUBLISH, new PublishValidator()); 1.229 + methodValidators.put(Method.REPLY, new ReplyValidator()); 1.230 + methodValidators.put(Method.REQUEST, new RequestValidator()); 1.231 + } 1.232 + 1.233 + /** 1.234 + * Default constructor. 1.235 + */ 1.236 + public VFreeBusy() { 1.237 + super(VFREEBUSY); 1.238 + getProperties().add(new DtStamp()); 1.239 + } 1.240 + 1.241 + /** 1.242 + * Constructor. 1.243 + * @param properties a list of properties 1.244 + */ 1.245 + public VFreeBusy(final PropertyList properties) { 1.246 + super(VFREEBUSY, properties); 1.247 + } 1.248 + 1.249 + /** 1.250 + * Constructs a new VFreeBusy instance with the specified start and end boundaries. This constructor should be used 1.251 + * for requesting busy time for a specified period. 1.252 + * @param start the starting boundary for the VFreeBusy 1.253 + * @param end the ending boundary for the VFreeBusy 1.254 + */ 1.255 + public VFreeBusy(final DateTime start, final DateTime end) { 1.256 + this(); 1.257 + 1.258 + // 4.8.2.4 Date/Time Start: 1.259 + // 1.260 + // Within the "VFREEBUSY" calendar component, this property defines the 1.261 + // start date and time for the free or busy time information. The time 1.262 + // MUST be specified in UTC time. 1.263 + getProperties().add(new DtStart(start, true)); 1.264 + 1.265 + // 4.8.2.2 Date/Time End 1.266 + // 1.267 + // Within the "VFREEBUSY" calendar component, this property defines the 1.268 + // end date and time for the free or busy time information. The time 1.269 + // MUST be specified in the UTC time format. The value MUST be later in 1.270 + // time than the value of the "DTSTART" property. 1.271 + getProperties().add(new DtEnd(end, true)); 1.272 + } 1.273 + 1.274 + /** 1.275 + * Constructs a new VFreeBusy instance with the specified start and end boundaries. This constructor should be used 1.276 + * for requesting free time for a specified duration in given period defined by the start date and end date. 1.277 + * @param start the starting boundary for the VFreeBusy 1.278 + * @param end the ending boundary for the VFreeBusy 1.279 + * @param duration the length of the period being requested 1.280 + */ 1.281 + public VFreeBusy(final DateTime start, final DateTime end, final Dur duration) { 1.282 + this(); 1.283 + 1.284 + // 4.8.2.4 Date/Time Start: 1.285 + // 1.286 + // Within the "VFREEBUSY" calendar component, this property defines the 1.287 + // start date and time for the free or busy time information. The time 1.288 + // MUST be specified in UTC time. 1.289 + getProperties().add(new DtStart(start, true)); 1.290 + 1.291 + // 4.8.2.2 Date/Time End 1.292 + // 1.293 + // Within the "VFREEBUSY" calendar component, this property defines the 1.294 + // end date and time for the free or busy time information. The time 1.295 + // MUST be specified in the UTC time format. The value MUST be later in 1.296 + // time than the value of the "DTSTART" property. 1.297 + getProperties().add(new DtEnd(end, true)); 1.298 + 1.299 + getProperties().add(new Duration(duration)); 1.300 + } 1.301 + 1.302 + /** 1.303 + * Constructs a new VFreeBusy instance representing a reply to the specified VFREEBUSY request according to the 1.304 + * specified list of components. 1.305 + * If the request argument has its duration set, then the result 1.306 + * represents a list of <em>free</em> times (that is, parameter FBTYPE 1.307 + * is set to FbType.FREE). 1.308 + * If the request argument does not have its duration set, then the result 1.309 + * represents a list of <em>busy</em> times. 1.310 + * @param request a VFREEBUSY request 1.311 + * @param components a component list used to initialise busy time 1.312 + * @throws ValidationException 1.313 + */ 1.314 + public VFreeBusy(final VFreeBusy request, final ComponentList components) { 1.315 + this(); 1.316 + 1.317 + final DtStart start = (DtStart) request.getProperty(Property.DTSTART); 1.318 + 1.319 + final DtEnd end = (DtEnd) request.getProperty(Property.DTEND); 1.320 + 1.321 + final Duration duration = (Duration) request.getProperty(Property.DURATION); 1.322 + 1.323 + // 4.8.2.4 Date/Time Start: 1.324 + // 1.325 + // Within the "VFREEBUSY" calendar component, this property defines the 1.326 + // start date and time for the free or busy time information. The time 1.327 + // MUST be specified in UTC time. 1.328 + getProperties().add(new DtStart(start.getDate(), true)); 1.329 + 1.330 + // 4.8.2.2 Date/Time End 1.331 + // 1.332 + // Within the "VFREEBUSY" calendar component, this property defines the 1.333 + // end date and time for the free or busy time information. The time 1.334 + // MUST be specified in the UTC time format. The value MUST be later in 1.335 + // time than the value of the "DTSTART" property. 1.336 + getProperties().add(new DtEnd(end.getDate(), true)); 1.337 + 1.338 + if (duration != null) { 1.339 + getProperties().add(new Duration(duration.getDuration())); 1.340 + // Initialise with all free time of at least the specified duration.. 1.341 + final DateTime freeStart = new DateTime(start.getDate()); 1.342 + final DateTime freeEnd = new DateTime(end.getDate()); 1.343 + final FreeBusy fb = new FreeTimeBuilder().start(freeStart) 1.344 + .end(freeEnd) 1.345 + .duration(duration.getDuration()) 1.346 + .components(components) 1.347 + .build(); 1.348 + if (fb != null && !fb.getPeriods().isEmpty()) { 1.349 + getProperties().add(fb); 1.350 + } 1.351 + } 1.352 + else { 1.353 + // initialise with all busy time for the specified period.. 1.354 + final DateTime busyStart = new DateTime(start.getDate()); 1.355 + final DateTime busyEnd = new DateTime(end.getDate()); 1.356 + final FreeBusy fb = new BusyTimeBuilder().start(busyStart) 1.357 + .end(busyEnd) 1.358 + .components(components) 1.359 + .build(); 1.360 + if (fb != null && !fb.getPeriods().isEmpty()) { 1.361 + getProperties().add(fb); 1.362 + } 1.363 + } 1.364 + } 1.365 + 1.366 + /** 1.367 + * Create a FREEBUSY property representing the busy time for the specified component list. If the component is not 1.368 + * applicable to FREEBUSY time, or if the component is outside the bounds of the start and end dates, null is 1.369 + * returned. If no valid busy periods are identified in the component an empty FREEBUSY property is returned (i.e. 1.370 + * empty period list). 1.371 + */ 1.372 + private class BusyTimeBuilder { 1.373 + 1.374 + private DateTime start; 1.375 + 1.376 + private DateTime end; 1.377 + 1.378 + private ComponentList components; 1.379 + 1.380 + public BusyTimeBuilder start(DateTime start) { 1.381 + this.start = start; 1.382 + return this; 1.383 + } 1.384 + 1.385 + public BusyTimeBuilder end(DateTime end) { 1.386 + this.end = end; 1.387 + return this; 1.388 + } 1.389 + 1.390 + public BusyTimeBuilder components(ComponentList components) { 1.391 + this.components = components; 1.392 + return this; 1.393 + } 1.394 + 1.395 + public FreeBusy build() { 1.396 + final PeriodList periods = getConsumedTime(components, start, end); 1.397 + final DateRange range = new DateRange(start, end); 1.398 + // periods must be in UTC time for freebusy.. 1.399 + periods.setUtc(true); 1.400 + for (final Iterator i = periods.iterator(); i.hasNext();) { 1.401 + final Period period = (Period) i.next(); 1.402 + // check if period outside bounds.. 1.403 + if (!range.intersects(period)) { 1.404 + periods.remove(period); 1.405 + } 1.406 + } 1.407 + return new FreeBusy(periods); 1.408 + } 1.409 + } 1.410 + 1.411 + /** 1.412 + * Create a FREEBUSY property representing the free time available of the specified duration for the given list of 1.413 + * components. component. If the component is not applicable to FREEBUSY time, or if the component is outside the 1.414 + * bounds of the start and end dates, null is returned. If no valid busy periods are identified in the component an 1.415 + * empty FREEBUSY property is returned (i.e. empty period list). 1.416 + */ 1.417 + private class FreeTimeBuilder { 1.418 + 1.419 + private DateTime start; 1.420 + 1.421 + private DateTime end; 1.422 + 1.423 + private Dur duration; 1.424 + 1.425 + private ComponentList components; 1.426 + 1.427 + public FreeTimeBuilder start(DateTime start) { 1.428 + this.start = start; 1.429 + return this; 1.430 + } 1.431 + 1.432 + public FreeTimeBuilder end(DateTime end) { 1.433 + this.end = end; 1.434 + return this; 1.435 + } 1.436 + 1.437 + private FreeTimeBuilder duration(Dur duration) { 1.438 + this.duration = duration; 1.439 + return this; 1.440 + } 1.441 + 1.442 + public FreeTimeBuilder components(ComponentList components) { 1.443 + this.components = components; 1.444 + return this; 1.445 + } 1.446 + 1.447 + public FreeBusy build() { 1.448 + final FreeBusy fb = new FreeBusy(); 1.449 + fb.getParameters().add(FbType.FREE); 1.450 + final PeriodList periods = getConsumedTime(components, start, end); 1.451 + final DateRange range = new DateRange(start, end); 1.452 + // Add final consumed time to avoid special-case end-of-list processing 1.453 + periods.add(new Period(end, end)); 1.454 + DateTime lastPeriodEnd = new DateTime(start); 1.455 + // where no time is consumed set the last period end as the range start.. 1.456 + for (final Iterator i = periods.iterator(); i.hasNext();) { 1.457 + final Period period = (Period) i.next(); 1.458 + 1.459 + // check if period outside bounds.. or period intersects with the end of the range.. 1.460 + if (range.contains(period) || 1.461 + (range.intersects(period) && period.getStart().after(range.getRangeStart()))) { 1.462 + 1.463 + // calculate duration between this period start and last period end.. 1.464 + final Duration freeDuration = new Duration(lastPeriodEnd, period.getStart()); 1.465 + if (freeDuration.getDuration().compareTo(duration) >= 0) { 1.466 + fb.getPeriods().add(new Period(lastPeriodEnd, freeDuration.getDuration())); 1.467 + } 1.468 + } 1.469 + 1.470 + if (period.getEnd().after(lastPeriodEnd)) { 1.471 + lastPeriodEnd = period.getEnd(); 1.472 + } 1.473 + } 1.474 + return fb; 1.475 + } 1.476 + } 1.477 + 1.478 + /** 1.479 + * Creates a list of periods representing the time consumed by the specified list of components. 1.480 + * @param components 1.481 + * @return 1.482 + */ 1.483 + private PeriodList getConsumedTime(final ComponentList components, final DateTime rangeStart, 1.484 + final DateTime rangeEnd) { 1.485 + 1.486 + final PeriodList periods = new PeriodList(); 1.487 + // only events consume time.. 1.488 + for (final Iterator i = components.getComponents(Component.VEVENT).iterator(); i.hasNext();) { 1.489 + final Component component = (Component) i.next(); 1.490 + periods.addAll(((VEvent) component).getConsumedTime(rangeStart, rangeEnd, false)); 1.491 + } 1.492 + return periods.normalise(); 1.493 + } 1.494 + 1.495 + /** 1.496 + * {@inheritDoc} 1.497 + */ 1.498 + public final void validate(final boolean recurse) throws ValidationException { 1.499 + 1.500 + if (!CompatibilityHints.isHintEnabled(CompatibilityHints.KEY_RELAXED_VALIDATION)) { 1.501 + 1.502 + // From "4.8.4.7 Unique Identifier": 1.503 + // Conformance: The property MUST be specified in the "VEVENT", "VTODO", 1.504 + // "VJOURNAL" or "VFREEBUSY" calendar components. 1.505 + PropertyValidator.getInstance().assertOne(Property.UID, 1.506 + getProperties()); 1.507 + 1.508 + // From "4.8.7.2 Date/Time Stamp": 1.509 + // Conformance: This property MUST be included in the "VEVENT", "VTODO", 1.510 + // "VJOURNAL" or "VFREEBUSY" calendar components. 1.511 + PropertyValidator.getInstance().assertOne(Property.DTSTAMP, 1.512 + getProperties()); 1.513 + } 1.514 + 1.515 + final PropertyValidator validator = PropertyValidator.getInstance(); 1.516 + 1.517 + /* 1.518 + * ; the following are optional, ; but MUST NOT occur more than once contact / dtstart / dtend / duration / 1.519 + * dtstamp / organizer / uid / url / 1.520 + */ 1.521 + validator.assertOneOrLess(Property.CONTACT, getProperties()); 1.522 + validator.assertOneOrLess(Property.DTSTART, getProperties()); 1.523 + validator.assertOneOrLess(Property.DTEND, getProperties()); 1.524 + validator.assertOneOrLess(Property.DURATION, getProperties()); 1.525 + validator.assertOneOrLess(Property.DTSTAMP, getProperties()); 1.526 + validator.assertOneOrLess(Property.ORGANIZER, getProperties()); 1.527 + validator.assertOneOrLess(Property.UID, getProperties()); 1.528 + validator.assertOneOrLess(Property.URL, getProperties()); 1.529 + 1.530 + /* 1.531 + * ; the following are optional, ; and MAY occur more than once attendee / comment / freebusy / rstatus / x-prop 1.532 + */ 1.533 + 1.534 + /* 1.535 + * The recurrence properties ("RRULE", "EXRULE", "RDATE", "EXDATE") are not permitted within a "VFREEBUSY" 1.536 + * calendar component. Any recurring events are resolved into their individual busy time periods using the 1.537 + * "FREEBUSY" property. 1.538 + */ 1.539 + validator.assertNone(Property.RRULE, getProperties()); 1.540 + validator.assertNone(Property.EXRULE, getProperties()); 1.541 + validator.assertNone(Property.RDATE, getProperties()); 1.542 + validator.assertNone(Property.EXDATE, getProperties()); 1.543 + 1.544 + // DtEnd value must be later in time that DtStart.. 1.545 + final DtStart dtStart = (DtStart) getProperty(Property.DTSTART); 1.546 + 1.547 + // 4.8.2.4 Date/Time Start: 1.548 + // 1.549 + // Within the "VFREEBUSY" calendar component, this property defines the 1.550 + // start date and time for the free or busy time information. The time 1.551 + // MUST be specified in UTC time. 1.552 + if (dtStart != null && !dtStart.isUtc()) { 1.553 + throw new ValidationException("DTSTART must be specified in UTC time"); 1.554 + } 1.555 + 1.556 + final DtEnd dtEnd = (DtEnd) getProperty(Property.DTEND); 1.557 + 1.558 + // 4.8.2.2 Date/Time End 1.559 + // 1.560 + // Within the "VFREEBUSY" calendar component, this property defines the 1.561 + // end date and time for the free or busy time information. The time 1.562 + // MUST be specified in the UTC time format. The value MUST be later in 1.563 + // time than the value of the "DTSTART" property. 1.564 + if (dtEnd != null && !dtEnd.isUtc()) { 1.565 + throw new ValidationException("DTEND must be specified in UTC time"); 1.566 + } 1.567 + 1.568 + if (dtStart != null && dtEnd != null 1.569 + && !dtStart.getDate().before(dtEnd.getDate())) { 1.570 + throw new ValidationException("Property [" + Property.DTEND 1.571 + + "] must be later in time than [" + Property.DTSTART + "]"); 1.572 + } 1.573 + 1.574 + if (recurse) { 1.575 + validateProperties(); 1.576 + } 1.577 + } 1.578 + 1.579 + /** 1.580 + * {@inheritDoc} 1.581 + */ 1.582 + protected Validator getValidator(Method method) { 1.583 + return (Validator) methodValidators.get(method); 1.584 + } 1.585 + 1.586 + /** 1.587 + * <pre> 1.588 + * Component/Property Presence 1.589 + * ------------------- ---------------------------------------------- 1.590 + * METHOD 1 MUST be "PUBLISH" 1.591 + * 1.592 + * VFREEBUSY 1+ 1.593 + * DTSTAMP 1 1.594 + * DTSTART 1 DateTime values must be in UTC 1.595 + * DTEND 1 DateTime values must be in UTC 1.596 + * FREEBUSY 1+ MUST be BUSYTIME. Multiple instances are 1.597 + * allowed. Multiple instances must be sorted 1.598 + * in ascending order 1.599 + * ORGANIZER 1 MUST contain the address of originator of 1.600 + * busy time data. 1.601 + * UID 1 1.602 + * COMMENT 0 or 1 1.603 + * CONTACT 0+ 1.604 + * X-PROPERTY 0+ 1.605 + * URL 0 or 1 Specifies busy time URL 1.606 + * 1.607 + * ATTENDEE 0 1.608 + * DURATION 0 1.609 + * REQUEST-STATUS 0 1.610 + * 1.611 + * X-COMPONENT 0+ 1.612 + * 1.613 + * VEVENT 0 1.614 + * VTODO 0 1.615 + * VJOURNAL 0 1.616 + * VTIMEZONE 0 1.617 + * VALARM 0 1.618 + * </pre> 1.619 + * 1.620 + */ 1.621 + private class PublishValidator implements Validator { 1.622 + 1.623 + private static final long serialVersionUID = 1L; 1.624 + 1.625 + public void validate() throws ValidationException { 1.626 + PropertyValidator.getInstance().assertOneOrMore(Property.FREEBUSY, getProperties()); 1.627 + 1.628 + PropertyValidator.getInstance().assertOne(Property.DTSTAMP, getProperties()); 1.629 + PropertyValidator.getInstance().assertOne(Property.DTSTART, getProperties()); 1.630 + PropertyValidator.getInstance().assertOne(Property.DTEND, getProperties()); 1.631 + PropertyValidator.getInstance().assertOne(Property.ORGANIZER, getProperties()); 1.632 + PropertyValidator.getInstance().assertOne(Property.UID, getProperties()); 1.633 + 1.634 + PropertyValidator.getInstance().assertOneOrLess(Property.COMMENT, getProperties()); 1.635 + PropertyValidator.getInstance().assertOneOrLess(Property.URL, getProperties()); 1.636 + 1.637 + PropertyValidator.getInstance().assertNone(Property.ATTENDEE, getProperties()); 1.638 + PropertyValidator.getInstance().assertNone(Property.DURATION, getProperties()); 1.639 + PropertyValidator.getInstance().assertNone(Property.REQUEST_STATUS, getProperties()); 1.640 + } 1.641 + } 1.642 + 1.643 + /** 1.644 + * <pre> 1.645 + * Component/Property Presence 1.646 + * ------------------- ---------------------------------------------- 1.647 + * METHOD 1 MUST be "REPLY" 1.648 + * 1.649 + * VFREEBUSY 1 1.650 + * ATTENDEE 1 (address of recipient replying) 1.651 + * DTSTAMP 1 1.652 + * DTEND 1 DateTime values must be in UTC 1.653 + * DTSTART 1 DateTime values must be in UTC 1.654 + * FREEBUSY 0+ (values MUST all be of the same data 1.655 + * type. Multiple instances are allowed. 1.656 + * Multiple instances MUST be sorted in 1.657 + * ascending order. Values MAY NOT overlap) 1.658 + * ORGANIZER 1 MUST be the request originator's address 1.659 + * UID 1 1.660 + * 1.661 + * COMMENT 0 or 1 1.662 + * CONTACT 0+ 1.663 + * REQUEST-STATUS 0+ 1.664 + * URL 0 or 1 (specifies busy time URL) 1.665 + * X-PROPERTY 0+ 1.666 + * DURATION 0 1.667 + * SEQUENCE 0 1.668 + * 1.669 + * X-COMPONENT 0+ 1.670 + * VALARM 0 1.671 + * VEVENT 0 1.672 + * VTODO 0 1.673 + * VJOURNAL 0 1.674 + * VTIMEZONE 0 1.675 + * </pre> 1.676 + * 1.677 + */ 1.678 + private class ReplyValidator implements Validator { 1.679 + 1.680 + private static final long serialVersionUID = 1L; 1.681 + 1.682 + public void validate() throws ValidationException { 1.683 + 1.684 + // FREEBUSY is 1+ in RFC2446 but 0+ in Calsify 1.685 + 1.686 + PropertyValidator.getInstance().assertOne(Property.ATTENDEE, getProperties()); 1.687 + PropertyValidator.getInstance().assertOne(Property.DTSTAMP, getProperties()); 1.688 + PropertyValidator.getInstance().assertOne(Property.DTEND, getProperties()); 1.689 + PropertyValidator.getInstance().assertOne(Property.DTSTART, getProperties()); 1.690 + PropertyValidator.getInstance().assertOne(Property.ORGANIZER, getProperties()); 1.691 + PropertyValidator.getInstance().assertOne(Property.UID, getProperties()); 1.692 + 1.693 + PropertyValidator.getInstance().assertOneOrLess(Property.COMMENT, getProperties()); 1.694 + PropertyValidator.getInstance().assertOneOrLess(Property.URL, getProperties()); 1.695 + 1.696 + PropertyValidator.getInstance().assertNone(Property.DURATION, getProperties()); 1.697 + PropertyValidator.getInstance().assertNone(Property.SEQUENCE, getProperties()); 1.698 + } 1.699 + } 1.700 + 1.701 + /** 1.702 + * METHOD:REQUEST Validator. 1.703 + * 1.704 + * <pre> 1.705 + * Component/Property Presence 1.706 + * ------------------- ---------------------------------------------- 1.707 + * METHOD 1 MUST be "REQUEST" 1.708 + * 1.709 + * VFREEBUSY 1 1.710 + * ATTENDEE 1+ contain the address of the calendar store 1.711 + * DTEND 1 DateTime values must be in UTC 1.712 + * DTSTAMP 1 1.713 + * DTSTART 1 DateTime values must be in UTC 1.714 + * ORGANIZER 1 MUST be the request originator's address 1.715 + * UID 1 1.716 + * COMMENT 0 or 1 1.717 + * CONTACT 0+ 1.718 + * X-PROPERTY 0+ 1.719 + * 1.720 + * FREEBUSY 0 1.721 + * DURATION 0 1.722 + * REQUEST-STATUS 0 1.723 + * URL 0 1.724 + * 1.725 + * X-COMPONENT 0+ 1.726 + * VALARM 0 1.727 + * VEVENT 0 1.728 + * VTODO 0 1.729 + * VJOURNAL 0 1.730 + * VTIMEZONE 0 1.731 + * </pre> 1.732 + * 1.733 + */ 1.734 + private class RequestValidator implements Validator { 1.735 + 1.736 + private static final long serialVersionUID = 1L; 1.737 + 1.738 + public void validate() throws ValidationException { 1.739 + PropertyValidator.getInstance().assertOneOrMore(Property.ATTENDEE, getProperties()); 1.740 + 1.741 + PropertyValidator.getInstance().assertOne(Property.DTEND, getProperties()); 1.742 + PropertyValidator.getInstance().assertOne(Property.DTSTAMP, getProperties()); 1.743 + PropertyValidator.getInstance().assertOne(Property.DTSTART, getProperties()); 1.744 + PropertyValidator.getInstance().assertOne(Property.ORGANIZER, getProperties()); 1.745 + PropertyValidator.getInstance().assertOne(Property.UID, getProperties()); 1.746 + 1.747 + PropertyValidator.getInstance().assertOneOrLess(Property.COMMENT, getProperties()); 1.748 + 1.749 + PropertyValidator.getInstance().assertNone(Property.FREEBUSY, getProperties()); 1.750 + PropertyValidator.getInstance().assertNone(Property.DURATION, getProperties()); 1.751 + PropertyValidator.getInstance().assertNone(Property.REQUEST_STATUS, getProperties()); 1.752 + PropertyValidator.getInstance().assertNone(Property.URL, getProperties()); 1.753 + } 1.754 + } 1.755 + 1.756 + /** 1.757 + * @return the CONTACT property or null if not specified 1.758 + */ 1.759 + public final Contact getContact() { 1.760 + return (Contact) getProperty(Property.CONTACT); 1.761 + } 1.762 + 1.763 + /** 1.764 + * @return the DTSTART propery or null if not specified 1.765 + */ 1.766 + public final DtStart getStartDate() { 1.767 + return (DtStart) getProperty(Property.DTSTART); 1.768 + } 1.769 + 1.770 + /** 1.771 + * @return the DTEND property or null if not specified 1.772 + */ 1.773 + public final DtEnd getEndDate() { 1.774 + return (DtEnd) getProperty(Property.DTEND); 1.775 + } 1.776 + 1.777 + /** 1.778 + * @return the DURATION property or null if not specified 1.779 + */ 1.780 + public final Duration getDuration() { 1.781 + return (Duration) getProperty(Property.DURATION); 1.782 + } 1.783 + 1.784 + /** 1.785 + * @return the DTSTAMP property or null if not specified 1.786 + */ 1.787 + public final DtStamp getDateStamp() { 1.788 + return (DtStamp) getProperty(Property.DTSTAMP); 1.789 + } 1.790 + 1.791 + /** 1.792 + * @return the ORGANIZER property or null if not specified 1.793 + */ 1.794 + public final Organizer getOrganizer() { 1.795 + return (Organizer) getProperty(Property.ORGANIZER); 1.796 + } 1.797 + 1.798 + /** 1.799 + * @return the URL property or null if not specified 1.800 + */ 1.801 + public final Url getUrl() { 1.802 + return (Url) getProperty(Property.URL); 1.803 + } 1.804 + 1.805 + /** 1.806 + * Returns the UID property of this component if available. 1.807 + * @return a Uid instance, or null if no UID property exists 1.808 + */ 1.809 + public final Uid getUid() { 1.810 + return (Uid) getProperty(Property.UID); 1.811 + } 1.812 +}