src/net/fortuna/ical4j/model/component/VFreeBusy.java

changeset 0
fb9019fb1bf7
child 3
73bdfa70b04e
     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 &quot;VFREEBUSY&quot; calendar component is defined by the
    1.81 + *     following notation:
    1.82 + *
    1.83 + *       freebusyc  = &quot;BEGIN&quot; &quot;:&quot; &quot;VFREEBUSY&quot; CRLF
    1.84 + *                    fbprop
    1.85 + *                    &quot;END&quot; &quot;:&quot; &quot;VFREEBUSY&quot; 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 &quot;VFREEBUSY&quot; 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 &quot;ATTENDEE&quot;
   1.108 + *     property specifies the calendar users whose free/busy time is being
   1.109 + *     requested; the &quot;ORGANIZER&quot; property specifies the calendar user who
   1.110 + *     is requesting the free/busy time; the &quot;DTSTART&quot; and &quot;DTEND&quot;
   1.111 + *     properties specify the window of time for which the free/busy time is
   1.112 + *     being requested; the &quot;UID&quot; and &quot;DTSTAMP&quot; 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 &quot;ATTENDEE&quot;
   1.116 + *     property specifies the calendar user responding to the free/busy time
   1.117 + *     request; the &quot;ORGANIZER&quot; property specifies the calendar user that
   1.118 + *     originally requested the free/busy time; the &quot;FREEBUSY&quot; property
   1.119 + *     specifies the free/busy time information (if it exists); and the
   1.120 + *     &quot;UID&quot; and &quot;DTSTAMP&quot; 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 &quot;ORGANIZER&quot; property specifies
   1.124 + *     the calendar user associated with the published busy time; the
   1.125 + *     &quot;DTSTART&quot; and &quot;DTEND&quot; properties specify an inclusive time window
   1.126 + *     that surrounds the busy time information; the &quot;FREEBUSY&quot; property
   1.127 + *     specifies the published busy time information; and the &quot;DTSTAMP&quot;
   1.128 + *     property specifies the date/time that iCalendar object was created.
   1.129 + *
   1.130 + *     The &quot;VFREEBUSY&quot; calendar component cannot be nested within another
   1.131 + *     calendar component. Multiple &quot;VFREEBUSY&quot; 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 &quot;VFREEBUSY&quot; 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 &quot;FREEBUSY&quot; property.
   1.141 + *     This property provides a terse representation of time periods. One or
   1.142 + *     more &quot;FREEBUSY&quot; properties can be specified in the &quot;VFREEBUSY&quot;
   1.143 + *     calendar component.
   1.144 + *
   1.145 + *     When present in a &quot;VFREEBUSY&quot; calendar component, the &quot;DTSTART&quot; and
   1.146 + *     &quot;DTEND&quot; properties SHOULD be specified prior to any &quot;FREEBUSY&quot;
   1.147 + *     properties. In a free time request, these properties can be used in
   1.148 + *     combination with the &quot;DURATION&quot; 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 (&quot;RRULE&quot;, &quot;EXRULE&quot;, &quot;RDATE&quot;, &quot;EXDATE&quot;) are
   1.152 + *     not permitted within a &quot;VFREEBUSY&quot; calendar component. Any recurring
   1.153 + *     events are resolved into their individual busy time periods using the
   1.154 + *     &quot;FREEBUSY&quot; property.
   1.155 + *
   1.156 + *     Example: The following is an example of a &quot;VFREEBUSY&quot; 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 &quot;VFREEBUSY&quot; 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 &quot;VFREEBUSY&quot; 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 +}

mercurial