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 = "Meeting at 1pm";
063 * VAlarm display = VAlarm.display(trigger, message);
064 *
065 * //email alarm
066 * Trigger trigger = ...
067 * String subject = "Reminder: Meeting at 1pm";
068 * String body = "Team,\n\nThe team meeting scheduled for 1pm is about to start. Snacks will be served!\n\nThanks,\nJohn";
069 * List<String> to = Arrays.asList("janedoe@example.com", "bobsmith@example.com");
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 }