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