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