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 = "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 * @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}