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