001    package biweekly.component;
002    
003    import java.util.Arrays;
004    import java.util.List;
005    
006    import biweekly.Warning;
007    import biweekly.parameter.Related;
008    import biweekly.property.Action;
009    import biweekly.property.Attachment;
010    import biweekly.property.Attendee;
011    import biweekly.property.DateDue;
012    import biweekly.property.DateEnd;
013    import biweekly.property.DateStart;
014    import biweekly.property.Description;
015    import biweekly.property.DurationProperty;
016    import biweekly.property.Repeat;
017    import biweekly.property.Summary;
018    import biweekly.property.Trigger;
019    import biweekly.util.Duration;
020    
021    /*
022     Copyright (c) 2013, Michael Angstadt
023     All rights reserved.
024    
025     Redistribution and use in source and binary forms, with or without
026     modification, are permitted provided that the following conditions are met: 
027    
028     1. Redistributions of source code must retain the above copyright notice, this
029     list of conditions and the following disclaimer. 
030     2. Redistributions in binary form must reproduce the above copyright notice,
031     this list of conditions and the following disclaimer in the documentation
032     and/or other materials provided with the distribution. 
033    
034     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
035     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
036     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
037     DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
038     ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
039     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
040     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
041     ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
042     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
043     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
044     */
045    
046    /**
047     * <p>
048     * Defines a reminder for an event or to-do task. This class contains static
049     * factory methods to aid in the construction of valid alarms.
050     * </p>
051     * 
052     * <p>
053     * <b>Examples:</b>
054     * 
055     * <pre class="brush:java">
056     * //audio alarm
057     * Trigger trigger = ...
058     * Attachment sound = ...
059     * VAlarm audio = VAlarm.audio(trigger, sound);
060     * 
061     * //display alarm
062     * Trigger trigger = ...
063     * String message = &quot;Meeting at 1pm&quot;;
064     * VAlarm display = VAlarm.display(trigger, message);
065     * 
066     * //email alarm
067     * Trigger trigger = ...
068     * String subject = &quot;Reminder: Meeting at 1pm&quot;;
069     * String body = &quot;Team,\n\nThe team meeting scheduled for 1pm is about to start.  Snacks will be served!\n\nThanks,\nJohn&quot;;
070     * List&lt;String&gt; to = Arrays.asList(&quot;janedoe@example.com&quot;, &quot;bobsmith@example.com&quot;);
071     * VAlarm email = VAlarm.email(trigger, subject, body, to);
072     * </pre>
073     * 
074     * </p>
075     * @author Michael Angstadt
076     * @rfc 5545 p.71-6
077     */
078    public class VAlarm extends ICalComponent {
079            /**
080             * Creates a new alarm. Consider using one of the static factory methods
081             * instead.
082             * @param action the alarm action (e.g. "email")
083             * @param trigger the trigger
084             */
085            public VAlarm(Action action, Trigger trigger) {
086                    setAction(action);
087                    setTrigger(trigger);
088            }
089    
090            /**
091             * Creates an audio alarm.
092             * @param trigger the trigger
093             * @return the alarm
094             */
095            public static VAlarm audio(Trigger trigger) {
096                    return audio(trigger, null);
097            }
098    
099            /**
100             * Creates an audio alarm.
101             * @param trigger the trigger
102             * @param sound a sound to play when the alarm triggers
103             * @return the alarm
104             */
105            public static VAlarm audio(Trigger trigger, Attachment sound) {
106                    VAlarm alarm = new VAlarm(Action.audio(), trigger);
107                    if (sound != null) {
108                            alarm.addAttachment(sound);
109                    }
110                    return alarm;
111            }
112    
113            /**
114             * Creates a display alarm.
115             * @param trigger the trigger
116             * @param displayText the display text
117             * @return the alarm
118             */
119            public static VAlarm display(Trigger trigger, String displayText) {
120                    VAlarm alarm = new VAlarm(Action.display(), trigger);
121                    alarm.setDescription(displayText);
122                    return alarm;
123            }
124    
125            /**
126             * Creates an email alarm.
127             * @param trigger the trigger
128             * @param subject the email subject
129             * @param body the email body
130             * @param recipients the email address(es) to send the alert to
131             * @return the alarm
132             */
133            public static VAlarm email(Trigger trigger, String subject, String body, String... recipients) {
134                    return email(trigger, subject, body, Arrays.asList(recipients));
135            }
136    
137            /**
138             * Creates an email alarm.
139             * @param trigger the trigger
140             * @param subject the email subject
141             * @param body the email body
142             * @param recipients the email address(es) to send the alert to
143             * @return the alarm
144             */
145            public static VAlarm email(Trigger trigger, String subject, String body, List<String> recipients) {
146                    VAlarm alarm = new VAlarm(Action.email(), trigger);
147                    alarm.setSummary(subject);
148                    alarm.setDescription(body);
149                    for (String recipient : recipients) {
150                            alarm.addAttendee(Attendee.email(recipient));
151                    }
152                    return alarm;
153            }
154    
155            /**
156             * Gets any attachments that are associated with the alarm.
157             * @return the attachments
158             * @rfc 5545 p.80-1
159             */
160            public List<Attachment> getAttachments() {
161                    return getProperties(Attachment.class);
162            }
163    
164            /**
165             * Adds an attachment to the alarm. Note that AUDIO alarms should only have
166             * 1 attachment.
167             * @param attachment the attachment to add
168             * @rfc 5545 p.80-1
169             */
170            public void addAttachment(Attachment attachment) {
171                    addProperty(attachment);
172            }
173    
174            /**
175             * <p>
176             * Gets a detailed description of the alarm. The description should be more
177             * detailed than the one provided by the {@link Summary} property.
178             * </p>
179             * <p>
180             * This property has different meanings, depending on the alarm action:
181             * <ul>
182             * <li>DISPLAY - the display text</li>
183             * <li>EMAIL - the body of the email message</li>
184             * <li>all others - a general description of the alarm</li>
185             * </ul>
186             * </p>
187             * @return the description or null if not set
188             * @rfc 5545 p.84-5
189             */
190            public Description getDescription() {
191                    return getProperty(Description.class);
192            }
193    
194            /**
195             * <p>
196             * Sets a detailed description of the alarm. The description should be more
197             * detailed than the one provided by the {@link Summary} property.
198             * </p>
199             * <p>
200             * This property has different meanings, depending on the alarm action:
201             * <ul>
202             * <li>DISPLAY - the display text</li>
203             * <li>EMAIL - the body of the email message</li>
204             * <li>all others - a general description of the alarm</li>
205             * </ul>
206             * </p>
207             * @param description the description or null to remove
208             * @rfc 5545 p.84-5
209             */
210            public void setDescription(Description description) {
211                    setProperty(Description.class, description);
212            }
213    
214            /**
215             * <p>
216             * Sets a detailed description of the alarm. The description should be more
217             * detailed than the one provided by the {@link Summary} property.
218             * </p>
219             * <p>
220             * This property has different meanings, depending on the alarm action:
221             * <ul>
222             * <li>DISPLAY - the display text</li>
223             * <li>EMAIL - the body of the email message</li>
224             * <li>all others - a general description of the alarm</li>
225             * </ul>
226             * </p>
227             * @param description the description or null to remove
228             * @return the property that was created
229             * @rfc 5545 p.84-5
230             */
231            public Description setDescription(String description) {
232                    Description prop = (description == null) ? null : new Description(description);
233                    setDescription(prop);
234                    return prop;
235            }
236    
237            /**
238             * <p>
239             * Gets the summary of the alarm.
240             * </p>
241             * <p>
242             * This property has different meanings, depending on the alarm action:
243             * <ul>
244             * <li>EMAIL - the subject line of the email</li>
245             * <li>all others - a one-line summary of the alarm</li>
246             * </ul>
247             * </p>
248             * @return the summary or null if not set
249             * @rfc 5545 p.93-4
250             */
251            public Summary getSummary() {
252                    return getProperty(Summary.class);
253            }
254    
255            /**
256             * <p>
257             * Sets the summary of the alarm.
258             * </p>
259             * <p>
260             * This property has different meanings, depending on the alarm action:
261             * <ul>
262             * <li>EMAIL - the subject line of the email</li>
263             * <li>all others - a one-line summary of the alarm</li>
264             * </ul>
265             * </p>
266             * @param summary the summary or null to remove
267             * @rfc 5545 p.93-4
268             */
269            public void setSummary(Summary summary) {
270                    setProperty(Summary.class, summary);
271            }
272    
273            /**
274             * <p>
275             * Sets the summary of the alarm.
276             * </p>
277             * <p>
278             * This property has different meanings, depending on the alarm action:
279             * <ul>
280             * <li>EMAIL - the subject line of the email</li>
281             * <li>all others - a one-line summary of the alarm</li>
282             * </ul>
283             * </p>
284             * @param summary the summary or null to remove
285             * @return the property that was created
286             * @rfc 5545 p.93-4
287             */
288            public Summary setSummary(String summary) {
289                    Summary prop = (summary == null) ? null : new Summary(summary);
290                    setSummary(prop);
291                    return prop;
292            }
293    
294            /**
295             * Gets the people who will be emailed when the alarm fires (only applicable
296             * for EMAIL alarms).
297             * @return the email recipients
298             * @rfc 5545 p.107-9
299             */
300            public List<Attendee> getAttendees() {
301                    return getProperties(Attendee.class);
302            }
303    
304            /**
305             * Adds a person who will be emailed when the alarm fires (only applicable
306             * for EMAIL alarms).
307             * @param attendee the email recipient
308             * @rfc 5545 p.107-9
309             */
310            public void addAttendee(Attendee attendee) {
311                    addProperty(attendee);
312            }
313    
314            /**
315             * Gets the type of action to invoke when the alarm is triggered.
316             * @return the action or null if not set
317             * @rfc 5545 p.132-3
318             */
319            public Action getAction() {
320                    return getProperty(Action.class);
321            }
322    
323            /**
324             * Sets the type of action to invoke when the alarm is triggered.
325             * @param action the action or null to remove
326             * @rfc 5545 p.132-3
327             */
328            public void setAction(Action action) {
329                    setProperty(Action.class, action);
330            }
331    
332            /**
333             * Gets the length of the pause between alarm repetitions.
334             * @return the duration or null if not set
335             * @rfc 5545 p.99
336             */
337            public DurationProperty getDuration() {
338                    return getProperty(DurationProperty.class);
339            }
340    
341            /**
342             * Sets the length of the pause between alarm repetitions.
343             * @param duration the duration or null to remove
344             * @rfc 5545 p.99
345             */
346            public void setDuration(DurationProperty duration) {
347                    setProperty(DurationProperty.class, duration);
348            }
349    
350            /**
351             * Sets the length of the pause between alarm repetitions.
352             * @param duration the duration or null to remove
353             * @return the property that was created
354             * @rfc 5545 p.99
355             */
356            public DurationProperty setDuration(Duration duration) {
357                    DurationProperty prop = (duration == null) ? null : new DurationProperty(duration);
358                    setDuration(prop);
359                    return prop;
360            }
361    
362            /**
363             * Gets the number of times an alarm should be repeated after its initial
364             * trigger.
365             * @return the repeat count or null if not set
366             * @rfc 5545 p.133
367             */
368            public Repeat getRepeat() {
369                    return getProperty(Repeat.class);
370            }
371    
372            /**
373             * Sets the number of times an alarm should be repeated after its initial
374             * trigger.
375             * @param repeat the repeat count or null to remove
376             * @rfc 5545 p.133
377             */
378            public void setRepeat(Repeat repeat) {
379                    setProperty(Repeat.class, repeat);
380            }
381    
382            /**
383             * Sets the number of times an alarm should be repeated after its initial
384             * trigger.
385             * @param count the repeat count (e.g. "2" to repeat it two more times after
386             * it was initially triggered, for a total of three times) or null to remove
387             * @return the property that was created
388             * @rfc 5545 p.133
389             */
390            public Repeat setRepeat(Integer count) {
391                    Repeat prop = (count == null) ? null : new Repeat(count);
392                    setRepeat(prop);
393                    return prop;
394            }
395    
396            /**
397             * Sets the repetition information for the alarm.
398             * @param count the repeat count (e.g. "2" to repeat it two more times after
399             * it was initially triggered, for a total of three times)
400             * @param pauseDuration the length of the pause between repeats
401             * @rfc 5545 p.133
402             */
403            public void setRepeat(int count, Duration pauseDuration) {
404                    Repeat repeat = new Repeat(count);
405                    DurationProperty duration = new DurationProperty(pauseDuration);
406                    setRepeat(repeat);
407                    setDuration(duration);
408            }
409    
410            /**
411             * Gets when the alarm will be triggered.
412             * @return the trigger time or null if not set
413             * @rfc 5545 p.133-6
414             */
415            public Trigger getTrigger() {
416                    return getProperty(Trigger.class);
417            }
418    
419            /**
420             * Sets when the alarm will be triggered.
421             * @param trigger the trigger time or null to remove
422             * @rfc 5545 p.133-6
423             */
424            public void setTrigger(Trigger trigger) {
425                    setProperty(Trigger.class, trigger);
426            }
427    
428            @SuppressWarnings("unchecked")
429            @Override
430            protected void validate(List<ICalComponent> components, List<Warning> warnings) {
431                    //all alarm types require Action and Trigger
432                    checkRequiredCardinality(warnings, Action.class, Trigger.class);
433    
434                    Action action = getAction();
435                    if (action != null) {
436                            if (action.isAudio()) {
437                                    if (getAttachments().size() > 1) {
438                                            warnings.add(Warning.validate(7));
439                                    }
440                            }
441    
442                            if (action.isDisplay()) {
443                                    checkRequiredCardinality(warnings, Description.class);
444                            }
445    
446                            if (action.isEmail()) {
447                                    checkRequiredCardinality(warnings, Summary.class, Description.class);
448                                    if (getAttendees().isEmpty()) {
449                                            warnings.add(Warning.validate(8));
450                                    }
451                            } else {
452                                    if (!getAttendees().isEmpty()) {
453                                            warnings.add(Warning.validate(9));
454                                    }
455                            }
456                    }
457    
458                    Trigger trigger = getTrigger();
459                    if (trigger != null) {
460                            Related related = trigger.getRelated();
461    
462                            if (related == null && trigger.getDuration() != null) {
463                                    warnings.add(Warning.validate(10));
464                            }
465    
466                            if (related != null) {
467                                    ICalComponent parent = components.get(components.size() - 1);
468                                    if (related == Related.START && parent.getProperty(DateStart.class) == null) {
469                                            warnings.add(Warning.validate(11));
470                                    }
471                                    if (related == Related.END) {
472                                            boolean noEndDate = false;
473    
474                                            if (parent instanceof VEvent) {
475                                                    noEndDate = (parent.getProperty(DateEnd.class) == null && (parent.getProperty(DateStart.class) == null || parent.getProperty(DurationProperty.class) == null));
476                                            } else if (parent instanceof VTodo) {
477                                                    noEndDate = (parent.getProperty(DateDue.class) == null && (parent.getProperty(DateStart.class) == null || parent.getProperty(DurationProperty.class) == null));
478                                            }
479    
480                                            if (noEndDate) {
481                                                    warnings.add(Warning.validate(12));
482                                            }
483                                    }
484                            }
485                    }
486            }
487    }