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 }