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