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 }