001package biweekly.component;
002
003import static biweekly.property.ValuedProperty.getValue;
004
005import java.util.Date;
006import java.util.List;
007
008import biweekly.ICalVersion;
009import biweekly.Warning;
010import biweekly.parameter.FreeBusyType;
011import biweekly.property.Attendee;
012import biweekly.property.Comment;
013import biweekly.property.Contact;
014import biweekly.property.DateEnd;
015import biweekly.property.DateStart;
016import biweekly.property.DateTimeStamp;
017import biweekly.property.FreeBusy;
018import biweekly.property.LastModified;
019import biweekly.property.Method;
020import biweekly.property.Organizer;
021import biweekly.property.RequestStatus;
022import biweekly.property.Uid;
023import biweekly.property.Url;
024import biweekly.util.Duration;
025import biweekly.util.ICalDate;
026
027/*
028 Copyright (c) 2013-2015, Michael Angstadt
029 All rights reserved.
030
031 Redistribution and use in source and binary forms, with or without
032 modification, are permitted provided that the following conditions are met: 
033
034 1. Redistributions of source code must retain the above copyright notice, this
035 list of conditions and the following disclaimer. 
036 2. Redistributions in binary form must reproduce the above copyright notice,
037 this list of conditions and the following disclaimer in the documentation
038 and/or other materials provided with the distribution. 
039
040 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
041 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
042 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
043 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
044 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
045 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
046 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
047 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
048 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
049 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
050 */
051
052/**
053 * <p>
054 * Defines a collection of time ranges that describe when a person is available
055 * and unavailable.
056 * </p>
057 * <p>
058 * <b>Examples:</b>
059 * 
060 * <pre class="brush:java">
061 * VFreeBusy freebusy = new VFreeBusy();
062 * 
063 * Date start = ...
064 * Date end = ...
065 * freebusy.addFreeBusy(FreeBusyType.FREE, start, end);
066 * 
067 * start = ...
068 * Duration duration = Duration.builder().hours(2).build();
069 * freebusy.addFreeBusy(FreeBusyType.BUSY, start, duration);
070 * </pre>
071 * 
072 * </p>
073 * @author Michael Angstadt
074 * @see <a href="http://tools.ietf.org/html/rfc5545#page-59">RFC 5545
075 * p.59-62</a>
076 * @see <a href="http://tools.ietf.org/html/rfc2445#page-58">RFC 2445
077 * p.58-60</a>
078 */
079public class VFreeBusy extends ICalComponent {
080        /**
081         * <p>
082         * Creates a new free/busy component.
083         * </p>
084         * <p>
085         * The following properties are auto-generated on object creation. These
086         * properties <b>must</b> be present in order for the free/busy component to
087         * be valid:
088         * <ul>
089         * <li>{@link Uid} - Set to a UUID.</li>
090         * <li>{@link DateTimeStamp} - Set to the current date-time.</li>
091         * </ul>
092         * </p>
093         */
094        public VFreeBusy() {
095                setUid(Uid.random());
096                setDateTimeStamp(new Date());
097        }
098
099        /**
100         * Gets the unique identifier for this free/busy entry. This component
101         * object comes populated with a UID on creation. This is a <b>required</b>
102         * property.
103         * @return the UID or null if not set
104         * @see <a href="http://tools.ietf.org/html/rfc5545#page-117">RFC 5545
105         * p.117-8</a>
106         */
107        public Uid getUid() {
108                return getProperty(Uid.class);
109        }
110
111        /**
112         * Sets the unique identifier for this free/busy entry. This component
113         * object comes populated with a UID on creation. This is a <b>required</b>
114         * property.
115         * @param uid the UID or null to remove
116         * @see <a href="http://tools.ietf.org/html/rfc5545#page-117">RFC 5545
117         * p.117-8</a>
118         */
119        public void setUid(Uid uid) {
120                setProperty(Uid.class, uid);
121        }
122
123        /**
124         * Sets the unique identifier for this free/busy entry. This component
125         * object comes populated with a UID on creation. This is a <b>required</b>
126         * property.
127         * @param uid the UID or null to remove
128         * @return the property that was created
129         * @see <a href="http://tools.ietf.org/html/rfc5545#page-117">RFC 5545
130         * p.117-8</a>
131         */
132        public Uid setUid(String uid) {
133                Uid prop = (uid == null) ? null : new Uid(uid);
134                setUid(prop);
135                return prop;
136        }
137
138        /**
139         * Gets either (a) the creation date of the iCalendar object (if the
140         * {@link Method} property is defined) or (b) the date that the free/busy
141         * entry was last modified (the {@link LastModified} property also holds
142         * this information). This free/busy object comes populated with a
143         * {@link DateTimeStamp} property that is set to the current time. This is a
144         * <b>required</b> property.
145         * @return the date time stamp or null if not set
146         * @see <a href="http://tools.ietf.org/html/rfc5545#page-137">RFC 5545
147         * p.137-8</a>
148         */
149        public DateTimeStamp getDateTimeStamp() {
150                return getProperty(DateTimeStamp.class);
151        }
152
153        /**
154         * Sets either (a) the creation date of the iCalendar object (if the
155         * {@link Method} property is defined) or (b) the date that the free/busy
156         * entry was last modified (the {@link LastModified} property also holds
157         * this information). This free/busy object comes populated with a
158         * {@link DateTimeStamp} property that is set to the current time. This is a
159         * <b>required</b> property.
160         * @param dateTimeStamp the date time stamp or null to remove
161         * @see <a href="http://tools.ietf.org/html/rfc5545#page-137">RFC 5545
162         * p.137-8</a>
163         */
164        public void setDateTimeStamp(DateTimeStamp dateTimeStamp) {
165                setProperty(DateTimeStamp.class, dateTimeStamp);
166        }
167
168        /**
169         * Sets either (a) the creation date of the iCalendar object (if the
170         * {@link Method} property is defined) or (b) the date that the free/busy
171         * entry was last modified (the {@link LastModified} property also holds
172         * this information). This free/busy object comes populated with a
173         * {@link DateTimeStamp} property that is set to the current time. This is a
174         * <b>required</b> property.
175         * @param dateTimeStamp the date time stamp or null to remove
176         * @return the property that was created
177         * @see <a href="http://tools.ietf.org/html/rfc5545#page-137">RFC 5545
178         * p.137-8</a>
179         */
180        public DateTimeStamp setDateTimeStamp(Date dateTimeStamp) {
181                DateTimeStamp prop = (dateTimeStamp == null) ? null : new DateTimeStamp(dateTimeStamp);
182                setDateTimeStamp(prop);
183                return prop;
184        }
185
186        /**
187         * Gets the contact associated with the free/busy entry.
188         * @return the contact or null if not set
189         * @see <a href="http://tools.ietf.org/html/rfc5545#page-109">RFC 5545
190         * p.109-11</a>
191         */
192        public Contact getContact() {
193                return getProperty(Contact.class);
194        }
195
196        /**
197         * Sets the contact for the free/busy entry.
198         * @param contact the contact or null to remove
199         * @see <a href="http://tools.ietf.org/html/rfc5545#page-109">RFC 5545
200         * p.109-11</a>
201         */
202        public void setContact(Contact contact) {
203                setProperty(Contact.class, contact);
204        }
205
206        /**
207         * Sets the contact for the free/busy entry.
208         * @param contact the contact (e.g. "ACME Co - (123) 555-1234")
209         * @return the property that was created
210         * @see <a href="http://tools.ietf.org/html/rfc5545#page-109">RFC 5545
211         * p.109-11</a>
212         */
213        public Contact addContact(String contact) {
214                Contact prop = new Contact(contact);
215                setContact(prop);
216                return prop;
217        }
218
219        /**
220         * Gets the date that the free/busy entry starts.
221         * @return the start date or null if not set
222         * @see <a href="http://tools.ietf.org/html/rfc5545#page-97">RFC 5545
223         * p.97-8</a>
224         */
225        public DateStart getDateStart() {
226                return getProperty(DateStart.class);
227        }
228
229        /**
230         * Sets the date that the free/busy entry starts.
231         * @param dateStart the start date or null to remove
232         * @see <a href="http://tools.ietf.org/html/rfc5545#page-97">RFC 5545
233         * p.97-8</a>
234         */
235        public void setDateStart(DateStart dateStart) {
236                setProperty(DateStart.class, dateStart);
237        }
238
239        /**
240         * Sets the date that the free/busy entry starts.
241         * @param dateStart the start date or null to remove
242         * @return the property that was created
243         * @see <a href="http://tools.ietf.org/html/rfc5545#page-97">RFC 5545
244         * p.97-8</a>
245         */
246        public DateStart setDateStart(Date dateStart) {
247                DateStart prop = (dateStart == null) ? null : new DateStart(dateStart);
248                setDateStart(prop);
249                return prop;
250        }
251
252        /**
253         * Gets the date that the free/busy entry ends.
254         * @return the end date or null if not set
255         * @see <a href="http://tools.ietf.org/html/rfc5545#page-95">RFC 5545
256         * p.95-6</a>
257         */
258        public DateEnd getDateEnd() {
259                return getProperty(DateEnd.class);
260        }
261
262        /**
263         * Sets the date that the free/busy entry ends.
264         * @param dateEnd the end date or null to remove
265         * @see <a href="http://tools.ietf.org/html/rfc5545#page-95">RFC 5545
266         * p.95-6</a>
267         */
268        public void setDateEnd(DateEnd dateEnd) {
269                setProperty(DateEnd.class, dateEnd);
270        }
271
272        /**
273         * Sets the date that the free/busy entry ends.
274         * @param dateEnd the end date or null to remove
275         * @return the property that was created
276         * @see <a href="http://tools.ietf.org/html/rfc5545#page-95">RFC 5545
277         * p.95-6</a>
278         */
279        public DateEnd setDateEnd(Date dateEnd) {
280                DateEnd prop = (dateEnd == null) ? null : new DateEnd(dateEnd);
281                setDateEnd(prop);
282                return prop;
283        }
284
285        /**
286         * Gets the person requesting the free/busy time.
287         * @return the person requesting the free/busy time or null if not set
288         * @see <a href="http://tools.ietf.org/html/rfc5545#page-111">RFC 5545
289         * p.111-2</a>
290         */
291        public Organizer getOrganizer() {
292                return getProperty(Organizer.class);
293        }
294
295        /**
296         * Sets the person requesting the free/busy time.
297         * @param organizer the person requesting the free/busy time or null to
298         * remove
299         * @see <a href="http://tools.ietf.org/html/rfc5545#page-111">RFC 5545
300         * p.111-2</a>
301         */
302        public void setOrganizer(Organizer organizer) {
303                setProperty(Organizer.class, organizer);
304        }
305
306        /**
307         * Sets the person requesting the free/busy time.
308         * @param email the email address of the person requesting the free/busy
309         * time (e.g. "johndoe@example.com") or null to remove
310         * @return the property that was created
311         * @see <a href="http://tools.ietf.org/html/rfc5545#page-111">RFC 5545
312         * p.111-2</a>
313         */
314        public Organizer setOrganizer(String email) {
315                Organizer prop = (email == null) ? null : new Organizer(null, email);
316                setOrganizer(prop);
317                return prop;
318        }
319
320        /**
321         * Gets a URL to a resource that contains additional information about the
322         * free/busy entry.
323         * @return the URL or null if not set
324         * @see <a href="http://tools.ietf.org/html/rfc5545#page-116">RFC 5545
325         * p.116-7</a>
326         */
327        public Url getUrl() {
328                return getProperty(Url.class);
329        }
330
331        /**
332         * Sets a URL to a resource that contains additional information about the
333         * free/busy entry.
334         * @param url the URL or null to remove
335         * @see <a href="http://tools.ietf.org/html/rfc5545#page-116">RFC 5545
336         * p.116-7</a>
337         */
338        public void setUrl(Url url) {
339                setProperty(Url.class, url);
340        }
341
342        /**
343         * Sets a URL to a resource that contains additional information about the
344         * free/busy entry.
345         * @param url the URL (e.g. "http://example.com/resource.ics") or null to
346         * remove
347         * @return the property that was created
348         * @see <a href="http://tools.ietf.org/html/rfc5545#page-116">RFC 5545
349         * p.116-7</a>
350         */
351        public Url setUrl(String url) {
352                Url prop = (url == null) ? null : new Url(url);
353                setUrl(prop);
354                return prop;
355        }
356
357        //
358        //zero or more
359        //      private List<Attendee> attendees;
360        //      private List<Comment> comments;
361        //      private List<FreeBusy> freeBusy;
362        //      private List<Rstatus> rstatus;
363
364        /**
365         * Gets the people who are involved in the free/busy entry.
366         * @return the attendees
367         * @see <a href="http://tools.ietf.org/html/rfc5545#page-107">RFC 5545
368         * p.107-9</a>
369         */
370        public List<Attendee> getAttendees() {
371                return getProperties(Attendee.class);
372        }
373
374        /**
375         * Adds a person who is involved in the free/busy entry.
376         * @param attendee the attendee
377         * @see <a href="http://tools.ietf.org/html/rfc5545#page-107">RFC 5545
378         * p.107-9</a>
379         */
380        public void addAttendee(Attendee attendee) {
381                addProperty(attendee);
382        }
383
384        /**
385         * Gets the comments attached to the free/busy entry.
386         * @return the comments
387         * @see <a href="http://tools.ietf.org/html/rfc5545#page-83">RFC 5545
388         * p.83-4</a>
389         */
390        public List<Comment> getComments() {
391                return getProperties(Comment.class);
392        }
393
394        /**
395         * Adds a comment to the free/busy entry.
396         * @param comment the comment to add
397         * @see <a href="http://tools.ietf.org/html/rfc5545#page-83">RFC 5545
398         * p.83-4</a>
399         */
400        public void addComment(Comment comment) {
401                addProperty(comment);
402        }
403
404        /**
405         * Adds a comment to the free/busy entry.
406         * @param comment the comment to add
407         * @return the property that was created
408         * @see <a href="http://tools.ietf.org/html/rfc5545#page-83">RFC 5545
409         * p.83-4</a>
410         */
411        public Comment addComment(String comment) {
412                Comment prop = new Comment(comment);
413                addComment(prop);
414                return prop;
415        }
416
417        /**
418         * Gets the person's availabilities over certain time periods (for example,
419         * "free" between 1pm-3pm, but "busy" between 3pm-4pm).
420         * @return the availabilities
421         * @see <a href="http://tools.ietf.org/html/rfc5545#page-100">RFC 5545
422         * p.100-1</a>
423         */
424        public List<FreeBusy> getFreeBusy() {
425                return getProperties(FreeBusy.class);
426        }
427
428        /**
429         * Adds a list of time periods for which the person is free or busy (for
430         * example, "free" between 1pm-3pm and 4pm-5pm). Note that a
431         * {@link FreeBusy} property can contain multiple time periods, but only one
432         * availability type (e.g. "busy").
433         * @param freeBusy the availabilities
434         * @see <a href="http://tools.ietf.org/html/rfc5545#page-100">RFC 5545
435         * p.100-1</a>
436         */
437        public void addFreeBusy(FreeBusy freeBusy) {
438                addProperty(freeBusy);
439        }
440
441        /**
442         * Adds a single time period for which the person is free or busy (for
443         * example, "free" between 1pm-3pm). This method will look for an existing
444         * property that has the given {@link FreeBusyType} and add the time period
445         * to it, or create a new property is one cannot be found.
446         * @param type the availability type (e.g. "free" or "busy")
447         * @param start the start date-time
448         * @param end the end date-time
449         * @return the property that was created/modified
450         * @see <a href="http://tools.ietf.org/html/rfc5545#page-100">RFC 5545
451         * p.100-1</a>
452         */
453        public FreeBusy addFreeBusy(FreeBusyType type, Date start, Date end) {
454                FreeBusy found = findByFbType(type);
455                found.addValue(start, end);
456                return found;
457        }
458
459        /**
460         * Adds a single time period for which the person is free or busy (for
461         * example, "free" for 2 hours after 1pm). This method will look for an
462         * existing property that has the given {@link FreeBusyType} and add the
463         * time period to it, or create a new property is one cannot be found.
464         * @param type the availability type (e.g. "free" or "busy")
465         * @param start the start date-time
466         * @param duration the length of time
467         * @return the property that was created/modified
468         * @see <a href="http://tools.ietf.org/html/rfc5545#page-100">RFC 5545
469         * p.100-1</a>
470         */
471        public FreeBusy addFreeBusy(FreeBusyType type, Date start, Duration duration) {
472                FreeBusy found = findByFbType(type);
473                found.addValue(start, duration);
474                return found;
475        }
476
477        private FreeBusy findByFbType(FreeBusyType type) {
478                FreeBusy found = null;
479
480                for (FreeBusy fb : getFreeBusy()) {
481                        if (fb.getType() == type) {
482                                found = fb;
483                                break;
484                        }
485                }
486
487                if (found == null) {
488                        found = new FreeBusy();
489                        found.setType(type);
490                        addFreeBusy(found);
491                }
492                return found;
493        }
494
495        /**
496         * Gets the response to a scheduling request.
497         * @return the response
498         * @see <a href="http://tools.ietf.org/html/rfc5545#page-141">RFC 5545
499         * p.141-3</a>
500         */
501        public RequestStatus getRequestStatus() {
502                return getProperty(RequestStatus.class);
503        }
504
505        /**
506         * Sets the response to a scheduling request.
507         * @param requestStatus the response
508         * @see <a href="http://tools.ietf.org/html/rfc5545#page-141">RFC 5545
509         * p.141-3</a>
510         */
511        public void setRequestStatus(RequestStatus requestStatus) {
512                setProperty(RequestStatus.class, requestStatus);
513        }
514
515        @SuppressWarnings("unchecked")
516        @Override
517        protected void validate(List<ICalComponent> components, ICalVersion version, List<Warning> warnings) {
518                if (version == ICalVersion.V1_0) {
519                        warnings.add(Warning.validate(48, version));
520                }
521
522                checkRequiredCardinality(warnings, Uid.class, DateTimeStamp.class);
523                checkOptionalCardinality(warnings, Contact.class, DateStart.class, DateEnd.class, Organizer.class, Url.class);
524
525                ICalDate dateStart = getValue(getDateStart());
526                ICalDate dateEnd = getValue(getDateEnd());
527
528                //DTSTART is required if DTEND exists
529                if (dateEnd != null && dateStart == null) {
530                        warnings.add(Warning.validate(15));
531                }
532
533                //DTSTART and DTEND must contain a time component
534                if (dateStart != null && !dateStart.hasTime()) {
535                        warnings.add(Warning.validate(20, DateStart.class.getSimpleName()));
536                }
537                if (dateEnd != null && !dateEnd.hasTime()) {
538                        warnings.add(Warning.validate(20, DateEnd.class.getSimpleName()));
539                }
540
541                //DTSTART must come before DTEND
542                if (dateStart != null && dateEnd != null && dateStart.compareTo(dateEnd) >= 0) {
543                        warnings.add(Warning.validate(16));
544                }
545        }
546}