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