001package biweekly.component;
002
003import java.util.Arrays;
004import java.util.List;
005
006import biweekly.ICalVersion;
007import biweekly.Warning;
008import biweekly.parameter.Related;
009import biweekly.property.Action;
010import biweekly.property.Attachment;
011import biweekly.property.Attendee;
012import biweekly.property.DateDue;
013import biweekly.property.DateEnd;
014import biweekly.property.DateStart;
015import biweekly.property.Description;
016import biweekly.property.DurationProperty;
017import biweekly.property.Repeat;
018import biweekly.property.Summary;
019import biweekly.property.Trigger;
020import biweekly.util.Duration;
021
022/*
023 Copyright (c) 2013-2015, 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 reminder for an event or to-do task. This class contains static
050 * factory methods to aid in the construction of valid alarms.
051 * </p>
052 * 
053 * <p>
054 * <b>Examples:</b>
055 * 
056 * <pre class="brush:java">
057 * //audio alarm
058 * Trigger trigger = ...
059 * Attachment sound = ...
060 * VAlarm audio = VAlarm.audio(trigger, sound);
061 * 
062 * //display alarm
063 * Trigger trigger = ...
064 * String message = &quot;Meeting at 1pm&quot;;
065 * VAlarm display = VAlarm.display(trigger, message);
066 * 
067 * //email alarm
068 * Trigger trigger = ...
069 * String subject = &quot;Reminder: Meeting at 1pm&quot;;
070 * String body = &quot;Team,\n\nThe team meeting scheduled for 1pm is about to start.  Snacks will be served!\n\nThanks,\nJohn&quot;;
071 * List&lt;String&gt; to = Arrays.asList(&quot;janedoe@example.com&quot;, &quot;bobsmith@example.com&quot;);
072 * VAlarm email = VAlarm.email(trigger, subject, body, to);
073 * </pre>
074 * 
075 * </p>
076 * @author Michael Angstadt
077 * @see <a href="http://tools.ietf.org/html/rfc5545#page-71">RFC 5545 p.71-6</a>
078 * @see <a href="http://tools.ietf.org/html/rfc2445#page-67">RFC 2445
079 * p.67-73</a>
080 */
081public class VAlarm extends ICalComponent {
082        /**
083         * Creates a new alarm. Consider using one of the static factory methods
084         * instead.
085         * @param action the alarm action (e.g. "email")
086         * @param trigger the trigger
087         */
088        public VAlarm(Action action, Trigger trigger) {
089                setAction(action);
090                setTrigger(trigger);
091        }
092
093        /**
094         * Creates an audio alarm.
095         * @param trigger the trigger
096         * @return the alarm
097         */
098        public static VAlarm audio(Trigger trigger) {
099                return audio(trigger, null);
100        }
101
102        /**
103         * Creates an audio alarm.
104         * @param trigger the trigger
105         * @param sound a sound to play when the alarm triggers
106         * @return the alarm
107         */
108        public static VAlarm audio(Trigger trigger, Attachment sound) {
109                VAlarm alarm = new VAlarm(Action.audio(), trigger);
110                if (sound != null) {
111                        alarm.addAttachment(sound);
112                }
113                return alarm;
114        }
115
116        /**
117         * Creates a display alarm.
118         * @param trigger the trigger
119         * @param displayText the display text
120         * @return the alarm
121         */
122        public static VAlarm display(Trigger trigger, String displayText) {
123                VAlarm alarm = new VAlarm(Action.display(), trigger);
124                alarm.setDescription(displayText);
125                return alarm;
126        }
127
128        /**
129         * Creates an email alarm.
130         * @param trigger the trigger
131         * @param subject the email subject
132         * @param body the email body
133         * @param recipients the email address(es) to send the alert to
134         * @return the alarm
135         */
136        public static VAlarm email(Trigger trigger, String subject, String body, String... recipients) {
137                return email(trigger, subject, body, Arrays.asList(recipients));
138        }
139
140        /**
141         * Creates a procedure alarm (vCal 1.0 only).
142         * @param trigger the trigger
143         * @param path the path or name of the procedure
144         * @return the alarm
145         */
146        public static VAlarm procedure(Trigger trigger, String path) {
147                VAlarm alarm = new VAlarm(Action.procedure(), trigger);
148                alarm.setDescription(path);
149                return alarm;
150        }
151
152        /**
153         * Creates an email alarm.
154         * @param trigger the trigger
155         * @param subject the email subject
156         * @param body the email body
157         * @param recipients the email address(es) to send the alert to
158         * @return the alarm
159         */
160        public static VAlarm email(Trigger trigger, String subject, String body, List<String> recipients) {
161                VAlarm alarm = new VAlarm(Action.email(), trigger);
162                alarm.setSummary(subject);
163                alarm.setDescription(body);
164                for (String recipient : recipients) {
165                        alarm.addAttendee(new Attendee(null, recipient));
166                }
167                return alarm;
168        }
169
170        /**
171         * Gets any attachments that are associated with the alarm.
172         * @return the attachments
173         * @see <a href="http://tools.ietf.org/html/rfc5545#page-80">RFC 5545
174         * p.80-1</a>
175         */
176        public List<Attachment> getAttachments() {
177                return getProperties(Attachment.class);
178        }
179
180        /**
181         * Adds an attachment to the alarm. Note that AUDIO alarms should only have
182         * 1 attachment.
183         * @param attachment the attachment to add
184         * @see <a href="http://tools.ietf.org/html/rfc5545#page-80">RFC 5545
185         * p.80-1</a>
186         */
187        public void addAttachment(Attachment attachment) {
188                addProperty(attachment);
189        }
190
191        /**
192         * <p>
193         * Gets a detailed description of the alarm. The description should be more
194         * detailed than the one provided by the {@link Summary} property.
195         * </p>
196         * <p>
197         * This property has different meanings, depending on the alarm action:
198         * <ul>
199         * <li>DISPLAY - the display text</li>
200         * <li>EMAIL - the body of the email message</li>
201         * <li>all others - a general description of the alarm</li>
202         * </ul>
203         * </p>
204         * @return the description or null if not set
205         * @see <a href="http://tools.ietf.org/html/rfc5545#page-84">RFC 5545
206         * p.84-5</a>
207         */
208        public Description getDescription() {
209                return getProperty(Description.class);
210        }
211
212        /**
213         * <p>
214         * Sets a detailed description of the alarm. The description should be more
215         * detailed than the one provided by the {@link Summary} property.
216         * </p>
217         * <p>
218         * This property has different meanings, depending on the alarm action:
219         * <ul>
220         * <li>DISPLAY - the display text</li>
221         * <li>EMAIL - the body of the email message</li>
222         * <li>all others - a general description of the alarm</li>
223         * </ul>
224         * </p>
225         * @param description the description or null to remove
226         * @see <a href="http://tools.ietf.org/html/rfc5545#page-84">RFC 5545
227         * p.84-5</a>
228         */
229        public void setDescription(Description description) {
230                setProperty(Description.class, description);
231        }
232
233        /**
234         * <p>
235         * Sets a detailed description of the alarm. The description should be more
236         * detailed than the one provided by the {@link Summary} property.
237         * </p>
238         * <p>
239         * This property has different meanings, depending on the alarm action:
240         * <ul>
241         * <li>DISPLAY - the display text</li>
242         * <li>EMAIL - the body of the email message</li>
243         * <li>all others - a general description of the alarm</li>
244         * </ul>
245         * </p>
246         * @param description the description or null to remove
247         * @return the property that was created
248         * @see <a href="http://tools.ietf.org/html/rfc5545#page-84">RFC 5545
249         * p.84-5</a>
250         */
251        public Description setDescription(String description) {
252                Description prop = (description == null) ? null : new Description(description);
253                setDescription(prop);
254                return prop;
255        }
256
257        /**
258         * <p>
259         * Gets the summary of the alarm.
260         * </p>
261         * <p>
262         * This property has different meanings, depending on the alarm action:
263         * <ul>
264         * <li>EMAIL - the subject line of the email</li>
265         * <li>all others - a one-line summary of the alarm</li>
266         * </ul>
267         * </p>
268         * @return the summary or null if not set
269         * @see <a href="http://tools.ietf.org/html/rfc5545#page-93">RFC 5545
270         * p.93-4</a>
271         */
272        public Summary getSummary() {
273                return getProperty(Summary.class);
274        }
275
276        /**
277         * <p>
278         * Sets the summary of the alarm.
279         * </p>
280         * <p>
281         * This property has different meanings, depending on the alarm action:
282         * <ul>
283         * <li>EMAIL - the subject line of the email</li>
284         * <li>all others - a one-line summary of the alarm</li>
285         * </ul>
286         * </p>
287         * @param summary the summary or null to remove
288         * @see <a href="http://tools.ietf.org/html/rfc5545#page-93">RFC 5545
289         * p.93-4</a>
290         */
291        public void setSummary(Summary summary) {
292                setProperty(Summary.class, summary);
293        }
294
295        /**
296         * <p>
297         * Sets the summary of the alarm.
298         * </p>
299         * <p>
300         * This property has different meanings, depending on the alarm action:
301         * <ul>
302         * <li>EMAIL - the subject line of the email</li>
303         * <li>all others - a one-line summary of the alarm</li>
304         * </ul>
305         * </p>
306         * @param summary the summary or null to remove
307         * @return the property that was created
308         * @see <a href="http://tools.ietf.org/html/rfc5545#page-93">RFC 5545
309         * p.93-4</a>
310         */
311        public Summary setSummary(String summary) {
312                Summary prop = (summary == null) ? null : new Summary(summary);
313                setSummary(prop);
314                return prop;
315        }
316
317        /**
318         * Gets the people who will be emailed when the alarm fires (only applicable
319         * for EMAIL alarms).
320         * @return the email recipients
321         * @see <a href="http://tools.ietf.org/html/rfc5545#page-107">RFC 5545
322         * p.107-9</a>
323         */
324        public List<Attendee> getAttendees() {
325                return getProperties(Attendee.class);
326        }
327
328        /**
329         * Adds a person who will be emailed when the alarm fires (only applicable
330         * for EMAIL alarms).
331         * @param attendee the email recipient
332         * @see <a href="http://tools.ietf.org/html/rfc5545#page-107">RFC 5545
333         * p.107-9</a>
334         */
335        public void addAttendee(Attendee attendee) {
336                addProperty(attendee);
337        }
338
339        /**
340         * Gets the type of action to invoke when the alarm is triggered.
341         * @return the action or null if not set
342         * @see <a href="http://tools.ietf.org/html/rfc5545#page-132">RFC 5545
343         * p.132-3</a>
344         */
345        public Action getAction() {
346                return getProperty(Action.class);
347        }
348
349        /**
350         * Sets the type of action to invoke when the alarm is triggered.
351         * @param action the action or null to remove
352         * @see <a href="http://tools.ietf.org/html/rfc5545#page-132">RFC 5545
353         * p.132-3</a>
354         */
355        public void setAction(Action action) {
356                setProperty(Action.class, action);
357        }
358
359        /**
360         * Gets the length of the pause between alarm repetitions.
361         * @return the duration or null if not set
362         * @see <a href="http://tools.ietf.org/html/rfc5545#page-99">RFC 5545
363         * p.99</a>
364         */
365        public DurationProperty getDuration() {
366                return getProperty(DurationProperty.class);
367        }
368
369        /**
370         * Sets the length of the pause between alarm repetitions.
371         * @param duration the duration or null to remove
372         * @see <a href="http://tools.ietf.org/html/rfc5545#page-99">RFC 5545
373         * p.99</a>
374         */
375        public void setDuration(DurationProperty duration) {
376                setProperty(DurationProperty.class, duration);
377        }
378
379        /**
380         * Sets the length of the pause between alarm repetitions.
381         * @param duration the duration or null to remove
382         * @return the property that was created
383         * @see <a href="http://tools.ietf.org/html/rfc5545#page-99">RFC 5545
384         * p.99</a>
385         */
386        public DurationProperty setDuration(Duration duration) {
387                DurationProperty prop = (duration == null) ? null : new DurationProperty(duration);
388                setDuration(prop);
389                return prop;
390        }
391
392        /**
393         * Gets the number of times an alarm should be repeated after its initial
394         * trigger.
395         * @return the repeat count or null if not set
396         * @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545
397         * p.133</a>
398         */
399        public Repeat getRepeat() {
400                return getProperty(Repeat.class);
401        }
402
403        /**
404         * Sets the number of times an alarm should be repeated after its initial
405         * trigger.
406         * @param repeat the repeat count or null to remove
407         * @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545
408         * p.133</a>
409         */
410        public void setRepeat(Repeat repeat) {
411                setProperty(Repeat.class, repeat);
412        }
413
414        /**
415         * Sets the number of times an alarm should be repeated after its initial
416         * trigger.
417         * @param count the repeat count (e.g. "2" to repeat it two more times after
418         * it was initially triggered, for a total of three times) or null to remove
419         * @return the property that was created
420         * @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545
421         * p.133</a>
422         */
423        public Repeat setRepeat(Integer count) {
424                Repeat prop = (count == null) ? null : new Repeat(count);
425                setRepeat(prop);
426                return prop;
427        }
428
429        /**
430         * Sets the repetition information for the alarm.
431         * @param count the repeat count (e.g. "2" to repeat it two more times after
432         * it was initially triggered, for a total of three times)
433         * @param pauseDuration the length of the pause between repeats
434         * @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545
435         * p.133</a>
436         */
437        public void setRepeat(int count, Duration pauseDuration) {
438                Repeat repeat = new Repeat(count);
439                DurationProperty duration = new DurationProperty(pauseDuration);
440                setRepeat(repeat);
441                setDuration(duration);
442        }
443
444        /**
445         * Gets when the alarm will be triggered.
446         * @return the trigger time or null if not set
447         * @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545
448         * p.133-6</a>
449         */
450        public Trigger getTrigger() {
451                return getProperty(Trigger.class);
452        }
453
454        /**
455         * Sets when the alarm will be triggered.
456         * @param trigger the trigger time or null to remove
457         * @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545
458         * p.133-6</a>
459         */
460        public void setTrigger(Trigger trigger) {
461                setProperty(Trigger.class, trigger);
462        }
463
464        @SuppressWarnings("unchecked")
465        @Override
466        protected void validate(List<ICalComponent> components, ICalVersion version, List<Warning> warnings) {
467                checkRequiredCardinality(warnings, Action.class, Trigger.class);
468
469                Action action = getAction();
470                if (action != null) {
471                        //AUDIO alarms should not have more than 1 attachment
472                        if (action.isAudio()) {
473                                if (getAttachments().size() > 1) {
474                                        warnings.add(Warning.validate(7));
475                                }
476                        }
477
478                        //DESCRIPTION is required for DISPLAY alarms 
479                        if (action.isDisplay()) {
480                                checkRequiredCardinality(warnings, Description.class);
481                        }
482
483                        if (action.isEmail()) {
484                                //SUMMARY and DESCRIPTION is required for EMAIL alarms
485                                checkRequiredCardinality(warnings, Summary.class, Description.class);
486
487                                //EMAIL alarms must have at least 1 ATTENDEE
488                                if (getAttendees().isEmpty()) {
489                                        warnings.add(Warning.validate(8));
490                                }
491                        } else {
492                                //only EMAIL alarms can have ATTENDEEs
493                                if (!getAttendees().isEmpty()) {
494                                        warnings.add(Warning.validate(9));
495                                }
496                        }
497
498                        if (action.isProcedure()) {
499                                checkRequiredCardinality(warnings, Description.class);
500                        }
501                }
502
503                Trigger trigger = getTrigger();
504                if (trigger != null) {
505                        Related related = trigger.getRelated();
506                        if (related != null) {
507                                ICalComponent parent = components.get(components.size() - 1);
508
509                                //if the TRIGGER is relative to DTSTART, confirm that DTSTART exists
510                                if (related == Related.START && parent.getProperty(DateStart.class) == null) {
511                                        warnings.add(Warning.validate(11));
512                                }
513
514                                //if the TRIGGER is relative to DTEND, confirm that DTEND (or DUE) exists
515                                if (related == Related.END) {
516                                        boolean noEndDate = false;
517
518                                        if (parent instanceof VEvent) {
519                                                noEndDate = (parent.getProperty(DateEnd.class) == null && (parent.getProperty(DateStart.class) == null || parent.getProperty(DurationProperty.class) == null));
520                                        } else if (parent instanceof VTodo) {
521                                                noEndDate = (parent.getProperty(DateDue.class) == null && (parent.getProperty(DateStart.class) == null || parent.getProperty(DurationProperty.class) == null));
522                                        }
523
524                                        if (noEndDate) {
525                                                warnings.add(Warning.validate(12));
526                                        }
527                                }
528                        }
529                }
530        }
531}