001package biweekly.component;
002
003import java.util.Arrays;
004import java.util.List;
005
006import biweekly.Warning;
007import biweekly.parameter.Related;
008import biweekly.property.Action;
009import biweekly.property.Attachment;
010import biweekly.property.Attendee;
011import biweekly.property.DateDue;
012import biweekly.property.DateEnd;
013import biweekly.property.DateStart;
014import biweekly.property.Description;
015import biweekly.property.DurationProperty;
016import biweekly.property.Repeat;
017import biweekly.property.Summary;
018import biweekly.property.Trigger;
019import 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 * @see <a href="http://tools.ietf.org/html/rfc5545#page-71">RFC 5545 p.71-6</a>
077 */
078public 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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-80">RFC 5545
159         * p.80-1</a>
160         */
161        public List<Attachment> getAttachments() {
162                return getProperties(Attachment.class);
163        }
164
165        /**
166         * Adds an attachment to the alarm. Note that AUDIO alarms should only have
167         * 1 attachment.
168         * @param attachment the attachment to add
169         * @see <a href="http://tools.ietf.org/html/rfc5545#page-80">RFC 5545
170         * p.80-1</a>
171         */
172        public void addAttachment(Attachment attachment) {
173                addProperty(attachment);
174        }
175
176        /**
177         * <p>
178         * Gets a detailed description of the alarm. The description should be more
179         * detailed than the one provided by the {@link Summary} property.
180         * </p>
181         * <p>
182         * This property has different meanings, depending on the alarm action:
183         * <ul>
184         * <li>DISPLAY - the display text</li>
185         * <li>EMAIL - the body of the email message</li>
186         * <li>all others - a general description of the alarm</li>
187         * </ul>
188         * </p>
189         * @return the description or null if not set
190         * @see <a href="http://tools.ietf.org/html/rfc5545#page-84">RFC 5545
191         * p.84-5</a>
192         */
193        public Description getDescription() {
194                return getProperty(Description.class);
195        }
196
197        /**
198         * <p>
199         * Sets a detailed description of the alarm. The description should be more
200         * detailed than the one provided by the {@link Summary} property.
201         * </p>
202         * <p>
203         * This property has different meanings, depending on the alarm action:
204         * <ul>
205         * <li>DISPLAY - the display text</li>
206         * <li>EMAIL - the body of the email message</li>
207         * <li>all others - a general description of the alarm</li>
208         * </ul>
209         * </p>
210         * @param description the description or null to remove
211         * @see <a href="http://tools.ietf.org/html/rfc5545#page-84">RFC 5545
212         * p.84-5</a>
213         */
214        public void setDescription(Description description) {
215                setProperty(Description.class, description);
216        }
217
218        /**
219         * <p>
220         * Sets a detailed description of the alarm. The description should be more
221         * detailed than the one provided by the {@link Summary} property.
222         * </p>
223         * <p>
224         * This property has different meanings, depending on the alarm action:
225         * <ul>
226         * <li>DISPLAY - the display text</li>
227         * <li>EMAIL - the body of the email message</li>
228         * <li>all others - a general description of the alarm</li>
229         * </ul>
230         * </p>
231         * @param description the description or null to remove
232         * @return the property that was created
233         * @see <a href="http://tools.ietf.org/html/rfc5545#page-84">RFC 5545
234         * p.84-5</a>
235         */
236        public Description setDescription(String description) {
237                Description prop = (description == null) ? null : new Description(description);
238                setDescription(prop);
239                return prop;
240        }
241
242        /**
243         * <p>
244         * Gets the summary of the alarm.
245         * </p>
246         * <p>
247         * This property has different meanings, depending on the alarm action:
248         * <ul>
249         * <li>EMAIL - the subject line of the email</li>
250         * <li>all others - a one-line summary of the alarm</li>
251         * </ul>
252         * </p>
253         * @return the summary or null if not set
254         * @see <a href="http://tools.ietf.org/html/rfc5545#page-93">RFC 5545
255         * p.93-4</a>
256         */
257        public Summary getSummary() {
258                return getProperty(Summary.class);
259        }
260
261        /**
262         * <p>
263         * Sets the summary of the alarm.
264         * </p>
265         * <p>
266         * This property has different meanings, depending on the alarm action:
267         * <ul>
268         * <li>EMAIL - the subject line of the email</li>
269         * <li>all others - a one-line summary of the alarm</li>
270         * </ul>
271         * </p>
272         * @param summary the summary or null to remove
273         * @see <a href="http://tools.ietf.org/html/rfc5545#page-93">RFC 5545
274         * p.93-4</a>
275         */
276        public void setSummary(Summary summary) {
277                setProperty(Summary.class, summary);
278        }
279
280        /**
281         * <p>
282         * Sets the summary of the alarm.
283         * </p>
284         * <p>
285         * This property has different meanings, depending on the alarm action:
286         * <ul>
287         * <li>EMAIL - the subject line of the email</li>
288         * <li>all others - a one-line summary of the alarm</li>
289         * </ul>
290         * </p>
291         * @param summary the summary or null to remove
292         * @return the property that was created
293         * @see <a href="http://tools.ietf.org/html/rfc5545#page-93">RFC 5545
294         * p.93-4</a>
295         */
296        public Summary setSummary(String summary) {
297                Summary prop = (summary == null) ? null : new Summary(summary);
298                setSummary(prop);
299                return prop;
300        }
301
302        /**
303         * Gets the people who will be emailed when the alarm fires (only applicable
304         * for EMAIL alarms).
305         * @return the email recipients
306         * @see <a href="http://tools.ietf.org/html/rfc5545#page-107">RFC 5545
307         * p.107-9</a>
308         */
309        public List<Attendee> getAttendees() {
310                return getProperties(Attendee.class);
311        }
312
313        /**
314         * Adds a person who will be emailed when the alarm fires (only applicable
315         * for EMAIL alarms).
316         * @param attendee the email recipient
317         * @see <a href="http://tools.ietf.org/html/rfc5545#page-107">RFC 5545
318         * p.107-9</a>
319         */
320        public void addAttendee(Attendee attendee) {
321                addProperty(attendee);
322        }
323
324        /**
325         * Gets the type of action to invoke when the alarm is triggered.
326         * @return the action or null if not set
327         * @see <a href="http://tools.ietf.org/html/rfc5545#page-132">RFC 5545
328         * p.132-3</a>
329         */
330        public Action getAction() {
331                return getProperty(Action.class);
332        }
333
334        /**
335         * Sets the type of action to invoke when the alarm is triggered.
336         * @param action the action or null to remove
337         * @see <a href="http://tools.ietf.org/html/rfc5545#page-132">RFC 5545
338         * p.132-3</a>
339         */
340        public void setAction(Action action) {
341                setProperty(Action.class, action);
342        }
343
344        /**
345         * Gets the length of the pause between alarm repetitions.
346         * @return the duration or null if not set
347         * @see <a href="http://tools.ietf.org/html/rfc5545#page-99">RFC 5545
348         * p.99</a>
349         */
350        public DurationProperty getDuration() {
351                return getProperty(DurationProperty.class);
352        }
353
354        /**
355         * Sets the length of the pause between alarm repetitions.
356         * @param duration the duration or null to remove
357         * @see <a href="http://tools.ietf.org/html/rfc5545#page-99">RFC 5545
358         * p.99</a>
359         */
360        public void setDuration(DurationProperty duration) {
361                setProperty(DurationProperty.class, duration);
362        }
363
364        /**
365         * Sets the length of the pause between alarm repetitions.
366         * @param duration the duration or null to remove
367         * @return the property that was created
368         * @see <a href="http://tools.ietf.org/html/rfc5545#page-99">RFC 5545
369         * p.99</a>
370         */
371        public DurationProperty setDuration(Duration duration) {
372                DurationProperty prop = (duration == null) ? null : new DurationProperty(duration);
373                setDuration(prop);
374                return prop;
375        }
376
377        /**
378         * Gets the number of times an alarm should be repeated after its initial
379         * trigger.
380         * @return the repeat count or null if not set
381         * @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545
382         * p.133</a>
383         */
384        public Repeat getRepeat() {
385                return getProperty(Repeat.class);
386        }
387
388        /**
389         * Sets the number of times an alarm should be repeated after its initial
390         * trigger.
391         * @param repeat the repeat count or null to remove
392         * @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545
393         * p.133</a>
394         */
395        public void setRepeat(Repeat repeat) {
396                setProperty(Repeat.class, repeat);
397        }
398
399        /**
400         * Sets the number of times an alarm should be repeated after its initial
401         * trigger.
402         * @param count the repeat count (e.g. "2" to repeat it two more times after
403         * it was initially triggered, for a total of three times) or null to remove
404         * @return the property that was created
405         * @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545
406         * p.133</a>
407         */
408        public Repeat setRepeat(Integer count) {
409                Repeat prop = (count == null) ? null : new Repeat(count);
410                setRepeat(prop);
411                return prop;
412        }
413
414        /**
415         * Sets the repetition information for the alarm.
416         * @param count the repeat count (e.g. "2" to repeat it two more times after
417         * it was initially triggered, for a total of three times)
418         * @param pauseDuration the length of the pause between repeats
419         * @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545
420         * p.133</a>
421         */
422        public void setRepeat(int count, Duration pauseDuration) {
423                Repeat repeat = new Repeat(count);
424                DurationProperty duration = new DurationProperty(pauseDuration);
425                setRepeat(repeat);
426                setDuration(duration);
427        }
428
429        /**
430         * Gets when the alarm will be triggered.
431         * @return the trigger time or null if not set
432         * @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545
433         * p.133-6</a>
434         */
435        public Trigger getTrigger() {
436                return getProperty(Trigger.class);
437        }
438
439        /**
440         * Sets when the alarm will be triggered.
441         * @param trigger the trigger time or null to remove
442         * @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545
443         * p.133-6</a>
444         */
445        public void setTrigger(Trigger trigger) {
446                setProperty(Trigger.class, trigger);
447        }
448
449        @SuppressWarnings("unchecked")
450        @Override
451        protected void validate(List<ICalComponent> components, List<Warning> warnings) {
452                checkRequiredCardinality(warnings, Action.class, Trigger.class);
453
454                Action action = getAction();
455                if (action != null) {
456                        //AUDIO alarms should not have more than 1 attachment
457                        if (action.isAudio()) {
458                                if (getAttachments().size() > 1) {
459                                        warnings.add(Warning.validate(7));
460                                }
461                        }
462
463                        //DESCRIPTION is required for DISPLAY alarms 
464                        if (action.isDisplay()) {
465                                checkRequiredCardinality(warnings, Description.class);
466                        }
467
468                        if (action.isEmail()) {
469                                //SUMMARY and DESCRIPTION is required for EMAIL alarms
470                                checkRequiredCardinality(warnings, Summary.class, Description.class);
471
472                                //EMAIL alarms must have at least 1 ATTENDEE
473                                if (getAttendees().isEmpty()) {
474                                        warnings.add(Warning.validate(8));
475                                }
476                        } else {
477                                //only EMAIL alarms can have ATTENDEEs
478                                if (!getAttendees().isEmpty()) {
479                                        warnings.add(Warning.validate(9));
480                                }
481                        }
482                }
483
484                Trigger trigger = getTrigger();
485                if (trigger != null) {
486                        Related related = trigger.getRelated();
487                        if (related != null) {
488                                ICalComponent parent = components.get(components.size() - 1);
489
490                                //if the TRIGGER is relative to DTSTART, confirm that DTSTART exists
491                                if (related == Related.START && parent.getProperty(DateStart.class) == null) {
492                                        warnings.add(Warning.validate(11));
493                                }
494
495                                //if the TRIGGER is relative to DTEND, confirm that DTEND (or DUE) exists
496                                if (related == Related.END) {
497                                        boolean noEndDate = false;
498
499                                        if (parent instanceof VEvent) {
500                                                noEndDate = (parent.getProperty(DateEnd.class) == null && (parent.getProperty(DateStart.class) == null || parent.getProperty(DurationProperty.class) == null));
501                                        } else if (parent instanceof VTodo) {
502                                                noEndDate = (parent.getProperty(DateDue.class) == null && (parent.getProperty(DateStart.class) == null || parent.getProperty(DurationProperty.class) == null));
503                                        }
504
505                                        if (noEndDate) {
506                                                warnings.add(Warning.validate(12));
507                                        }
508                                }
509                        }
510                }
511        }
512}