001 package biweekly.parameter; 002 003 import java.util.ArrayList; 004 import java.util.List; 005 006 import biweekly.ICalDataType; 007 import biweekly.component.VTimezone; 008 import biweekly.property.FreeBusy; 009 import biweekly.property.RecurrenceId; 010 import biweekly.property.RelatedTo; 011 import biweekly.property.TimezoneId; 012 import biweekly.property.Trigger; 013 import biweekly.util.ListMultimap; 014 015 /* 016 Copyright (c) 2013, Michael Angstadt 017 All rights reserved. 018 019 Redistribution and use in source and binary forms, with or without 020 modification, are permitted provided that the following conditions are met: 021 022 1. Redistributions of source code must retain the above copyright notice, this 023 list of conditions and the following disclaimer. 024 2. Redistributions in binary form must reproduce the above copyright notice, 025 this list of conditions and the following disclaimer in the documentation 026 and/or other materials provided with the distribution. 027 028 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 029 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 030 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 031 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 032 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 033 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 034 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 035 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 036 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 037 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 038 */ 039 040 /** 041 * Contains the list of parameters that belong to a property. 042 * @author Michael Angstadt 043 */ 044 public class ICalParameters extends ListMultimap<String, String> { 045 public static final String CN = "CN"; 046 public static final String ALTREP = "ALTREP"; 047 public static final String CUTYPE = "CUTYPE"; 048 public static final String DELEGATED_FROM = "DELEGATED-FROM"; 049 public static final String DELEGATED_TO = "DELEGATED-TO"; 050 public static final String DIR = "DIR"; 051 public static final String ENCODING = "ENCODING"; 052 public static final String FMTTYPE = "FMTTYPE"; 053 public static final String FBTYPE = "FBTYPE"; 054 public static final String LANGUAGE = "LANGUAGE"; 055 public static final String MEMBER = "MEMBER"; 056 public static final String PARTSTAT = "PARTSTAT"; 057 public static final String RANGE = "RANGE"; 058 public static final String RELATED = "RELATED"; 059 public static final String RELTYPE = "RELTYPE"; 060 public static final String ROLE = "ROLE"; 061 public static final String RSVP = "RSVP"; 062 public static final String SENT_BY = "SENT-BY"; 063 public static final String TZID = "TZID"; 064 public static final String VALUE = "VALUE"; 065 066 /** 067 * Creates a parameters list. 068 */ 069 public ICalParameters() { 070 super(0); //initialize map size to 0 because most properties don't use any parameters 071 } 072 073 /** 074 * Copies an existing parameters list. 075 * @param parameters the list to copy 076 */ 077 public ICalParameters(ICalParameters parameters) { 078 super(parameters); 079 } 080 081 /** 082 * Gets a URI pointing to additional information about the entity 083 * represented by the property. 084 * @return the URI or null if not set 085 * @rfc 5545 p.14-5 086 */ 087 public String getAltRepresentation() { 088 return first(ALTREP); 089 } 090 091 /** 092 * Sets a URI pointing to additional information about the entity 093 * represented by the property. 094 * @param uri the URI or null to remove 095 * @rfc 5545 p.14-5 096 */ 097 public void setAltRepresentation(String uri) { 098 replace(ALTREP, uri); 099 } 100 101 /** 102 * Gets the display name of a person. 103 * @return the display name (e.g. "John Doe") or null if not set 104 * @rfc 5545 p.15-6 105 */ 106 public String getCommonName() { 107 return first(CN); 108 } 109 110 /** 111 * Sets the display name of a person. 112 * @param cn the display name (e.g. "John Doe") or null to remove 113 * @rfc 5545 p.15-6 114 */ 115 public void setCommonName(String cn) { 116 replace(CN, cn); 117 } 118 119 /** 120 * Gets the type of user an attendee is (for example, an "individual" or a 121 * "room"). 122 * @return the calendar user type or null if not set 123 * @rfc 5545 p.16 124 */ 125 public CalendarUserType getCalendarUserType() { 126 String value = first(CUTYPE); 127 return (value == null) ? null : CalendarUserType.get(value); 128 } 129 130 /** 131 * Sets the type of user an attendee is (for example, an "individual" or a 132 * "room"). 133 * @param cutype the calendar user type or null to remove 134 * @rfc 5545 p.16 135 */ 136 public void setCalendarUserType(CalendarUserType cutype) { 137 replace(CUTYPE, (cutype == null) ? null : cutype.getValue()); 138 } 139 140 /** 141 * Gets the people who have delegated their responsibility to an attendee. 142 * @return the delegators (typically email URIs, e.g. 143 * "mailto:janedoe@example.com") 144 * @rfc 5545 p.17 145 */ 146 public List<String> getDelegatedFrom() { 147 return get(DELEGATED_FROM); 148 } 149 150 /** 151 * Adds a person who has delegated his or her responsibility to an attendee. 152 * @param uri the delegator (typically an email URI, e.g. 153 * "mailto:janedoe@example.com") 154 * @rfc 5545 p.17 155 */ 156 public void addDelegatedFrom(String uri) { 157 put(DELEGATED_FROM, uri); 158 } 159 160 /** 161 * Removes a person who has delegated his or her responsibility to an 162 * attendee. 163 * @param uri the delegator to remove (typically an email URI, e.g. 164 * "mailto:janedoe@example.com") 165 * @rfc 5545 p.17 166 */ 167 public void removeDelegatedFrom(String uri) { 168 remove(DELEGATED_FROM, uri); 169 } 170 171 /** 172 * Removes everyone who has delegated his or her responsibility to an 173 * attendee. 174 * @rfc 5545 p.17 175 */ 176 public void removeDelegatedFrom() { 177 removeAll(DELEGATED_FROM); 178 } 179 180 /** 181 * Gets the people to which an attendee has delegated his or her 182 * responsibility. 183 * @return the delegatees (typically email URIs, e.g. 184 * "mailto:janedoe@example.com") 185 * @rfc 5545 p.17-8 186 */ 187 public List<String> getDelegatedTo() { 188 return get(DELEGATED_TO); 189 } 190 191 /** 192 * Adds a person to which an attendee has delegated his or her 193 * responsibility. 194 * @param uri the delegatee (typically an email URI, e.g. 195 * "mailto:janedoe@example.com") 196 * @rfc 5545 p.17-8 197 */ 198 public void addDelegatedTo(String uri) { 199 put(DELEGATED_TO, uri); 200 } 201 202 /** 203 * Removes a person to which an attendee has delegated his or her 204 * responsibility. 205 * @param uri the delegatee to remove (typically an email URI, e.g. 206 * "mailto:janedoe@example.com") 207 * @rfc 5545 p.17-8 208 */ 209 public void removeDelegatedTo(String uri) { 210 remove(DELEGATED_TO, uri); 211 } 212 213 /** 214 * Removes everyone to which an attendee has delegated his or her 215 * responsibility. 216 * @rfc 5545 p.17-8 217 */ 218 public void removeDelegatedTo() { 219 removeAll(DELEGATED_TO); 220 } 221 222 /** 223 * Gets a URI that contains additional information about the person. 224 * @return the URI (e.g. an LDAP URI) or null if not set 225 * @rfc 5545 p.18 226 */ 227 public String getDirectoryEntry() { 228 return first(DIR); 229 } 230 231 /** 232 * Sets a URI that contains additional information about the person. 233 * @param uri the URI (e.g. an LDAP URI) or null to remove 234 * @rfc 5545 p.18 235 */ 236 public void setDirectoryEntry(String uri) { 237 replace(DIR, uri); 238 } 239 240 /** 241 * Gets the encoding of the property value (for example, "base64"). 242 * @return the encoding or null if not set 243 * @rfc 5545 p.18-9 244 */ 245 public Encoding getEncoding() { 246 String value = first(ENCODING); 247 return (value == null) ? null : Encoding.get(value); 248 } 249 250 /** 251 * Sets the encoding of the property value (for example, "base64"). 252 * @param encoding the encoding or null to remove 253 * @rfc 5545 p.18-9 254 */ 255 public void setEncoding(Encoding encoding) { 256 replace(ENCODING, (encoding == null) ? null : encoding.getValue()); 257 } 258 259 /** 260 * Gets the content-type of the property's value. 261 * @return the content type (e.g. "image/png") or null if not set 262 * @rfc 5545 p.19-20 263 */ 264 public String getFormatType() { 265 return first(FMTTYPE); 266 } 267 268 /** 269 * Sets the content-type of the property's value. 270 * @param formatType the content type (e.g. "image/png") or null to remove 271 * @rfc 5545 p.19-20 272 */ 273 public void setFormatType(String formatType) { 274 replace(FMTTYPE, formatType); 275 } 276 277 /** 278 * Gets the person's status over the time periods that are specified in a 279 * {@link FreeBusy} property (for example, "free" or "busy"). If not set, 280 * the user should be considered "busy". 281 * @return the type or null if not set 282 * @rfc 5545 p.20 283 */ 284 public FreeBusyType getFreeBusyType() { 285 String value = first(FBTYPE); 286 return (value == null) ? null : FreeBusyType.get(value); 287 } 288 289 /** 290 * Sets the person's status over the time periods that are specified in a 291 * {@link FreeBusy} property (for example, "free" or "busy"). If not set, 292 * the user should be considered "busy". 293 * @param fbType the type or null to remove 294 * @rfc 5545 p.20 295 */ 296 public void setFreeBusyType(FreeBusyType fbType) { 297 replace(FBTYPE, (fbType == null) ? null : fbType.getValue()); 298 } 299 300 /** 301 * Gets the language that the property value is written in. 302 * @return the language (e.g. "en" for English) or null if not set 303 * @rfc 5545 p.21 304 */ 305 public String getLanguage() { 306 return first(LANGUAGE); 307 } 308 309 /** 310 * Sets the language that the property value is written in. 311 * @param language the language (e.g. "en" for English) or null to remove 312 * @rfc 5545 p.21 313 */ 314 public void setLanguage(String language) { 315 replace(LANGUAGE, language); 316 } 317 318 /** 319 * Adds a group that an attendee is a member of. 320 * @param uri the group URI (typically, an email address URI, e.g. 321 * "mailto:mailinglist@example.com") 322 * @rfc 5545 p.21-2 323 */ 324 public void addMember(String uri) { 325 put(MEMBER, uri); 326 } 327 328 /** 329 * Gets the groups that an attendee is a member of. 330 * @return the group URIs (typically, these are email address URIs, e.g. 331 * "mailto:mailinglist@example.com") 332 * @rfc 5545 p.21-2 333 */ 334 public List<String> getMembers() { 335 return get(MEMBER); 336 } 337 338 /** 339 * Removes a group that an attendee is a member of. 340 * @param uri the group URI to remove (typically, an email address URI, e.g. 341 * "mailto:mailinglist@example.com") 342 * @rfc 5545 p.21-2 343 */ 344 public void removeMember(String uri) { 345 remove(MEMBER, uri); 346 } 347 348 /** 349 * Removes all groups that an attendee is a member of. 350 * @rfc 5545 p.21-2 351 */ 352 public void removeMembers() { 353 removeAll(MEMBER); 354 } 355 356 /** 357 * Gets an attendee's level of participation. 358 * @return the participation status or null if not set 359 * @rfc 5545 p.22-3 360 */ 361 public ParticipationStatus getParticipationStatus() { 362 String value = first(PARTSTAT); 363 return (value == null) ? null : ParticipationStatus.get(value); 364 } 365 366 /** 367 * Sets an attendee's level of participation. 368 * @param status the participation status or null to remove 369 * @rfc 5545 p.22-3 370 */ 371 public void setParticipationStatus(ParticipationStatus status) { 372 replace(PARTSTAT, (status == null) ? null : status.getValue()); 373 } 374 375 /** 376 * Gets the effective range of recurrence instances from the instance 377 * specified by a {@link RecurrenceId} property. 378 * @return the range or null if not set 379 * @rfc 5545 p.23-4 380 */ 381 public Range getRange() { 382 String value = first(RANGE); 383 return (value == null) ? null : Range.get(value); 384 } 385 386 /** 387 * Sets the effective range of recurrence instances from the instance 388 * specified by a {@link RecurrenceId} property. 389 * @param range the range or null to remove 390 * @rfc 5545 p.23-4 391 */ 392 public void setRange(Range range) { 393 replace(RANGE, (range == null) ? null : range.getValue()); 394 } 395 396 /** 397 * Gets the date-time field that the duration in a {@link Trigger} property 398 * is relative to. 399 * @return the field or null if not set 400 * @rfc 5545 p.24 401 */ 402 public Related getRelated() { 403 String value = first(RELATED); 404 return (value == null) ? null : Related.get(value); 405 } 406 407 /** 408 * Sets the date-time field that the duration in a {@link Trigger} property 409 * is relative to. 410 * @param related the field or null to remove 411 * @rfc 5545 p.24 412 */ 413 public void setRelated(Related related) { 414 replace(RELATED, (related == null) ? null : related.getValue()); 415 } 416 417 /** 418 * Gets the relationship type of a {@link RelatedTo} property. 419 * @return the relationship type (e.g. "child") or null if not set 420 * @rfc 5545 p.25 421 */ 422 public RelationshipType getRelationshipType() { 423 String value = first(RELTYPE); 424 return (value == null) ? null : RelationshipType.get(value); 425 } 426 427 /** 428 * Sets the relationship type of a {@link RelatedTo} property. 429 * @param relationshipType the relationship type (e.g. "child") or null to 430 * remove 431 * @rfc 5545 p.25 432 */ 433 public void setRelationshipType(RelationshipType relationshipType) { 434 replace(RELTYPE, (relationshipType == null) ? null : relationshipType.getValue()); 435 } 436 437 /** 438 * Gets an attendee's role (for example, "chair" or "required participant"). 439 * @return the role or null if not set 440 * @rfc 5545 p.25-6 441 */ 442 public Role getRole() { 443 String value = first(ROLE); 444 return (value == null) ? null : Role.get(value); 445 } 446 447 /** 448 * Sets an attendee's role (for example, "chair" or "required participant"). 449 * @param role the role or null to remove 450 * @rfc 5545 p.25-6 451 */ 452 public void setRole(Role role) { 453 replace(ROLE, (role == null) ? null : role.getValue()); 454 } 455 456 /** 457 * Gets whether the organizer requests a response from an attendee. 458 * @throws IllegalStateException if the parameter value is malformed and 459 * cannot be parsed 460 * @return true if an RSVP is requested, false if not, null if not set 461 * @rfc 5545 p.26-7 462 */ 463 public Boolean getRsvp() { 464 String value = first(RSVP); 465 466 if (value == null) { 467 return null; 468 } 469 if ("true".equalsIgnoreCase(value)) { 470 return true; 471 } 472 if ("false".equalsIgnoreCase(value)) { 473 return false; 474 } 475 throw new IllegalStateException(RSVP + " parameter value is malformed and could not be parsed. Retrieve its raw text value instead."); 476 } 477 478 /** 479 * Sets whether the organizer requests a response from an attendee. 480 * @param rsvp true if an RSVP has been requested, false if not, null to 481 * remove 482 * @rfc 5545 p.26-7 483 */ 484 public void setRsvp(Boolean rsvp) { 485 replace(RSVP, (rsvp == null) ? null : rsvp.toString().toUpperCase()); 486 } 487 488 /** 489 * Gets a person that is acting on behalf of the person defined in the 490 * property. 491 * @return a URI representing the person (typically, an email URI, e.g. 492 * "mailto:janedoe@example.com") or null if not set 493 * @rfc 5545 p.27 494 */ 495 public String getSentBy() { 496 return first(SENT_BY); 497 } 498 499 /** 500 * Sets a person that is acting on behalf of the person defined in the 501 * property. 502 * @param uri a URI representing the person (typically, an email URI, e.g. 503 * "mailto:janedoe@example.com") or null to remove 504 * @rfc 5545 p.27 505 */ 506 public void setSentBy(String uri) { 507 replace(SENT_BY, uri); 508 } 509 510 /** 511 * Gets the timezone identifier. This either (a) references the 512 * {@link TimezoneId} property of a {@link VTimezone} component, or (b) 513 * specifies a globally-defined timezone (e.g. "America/New_York"). For a 514 * list of globally-defined timezones, see the <a 515 * href="http://www.twinsun.com/tz/tz-link.htm">TZ database</a>. 516 * @return the timezone identifier or null if not set 517 * @rfc 5545 p.27-8 518 */ 519 public String getTimezoneId() { 520 return first(TZID); 521 } 522 523 /** 524 * Sets the timezone identifier. This either (a) references the 525 * {@link TimezoneId} property of a {@link VTimezone} component, or (b) 526 * specifies a globally-defined timezone (e.g. "America/New_York"). For a 527 * list of globally-defined timezones, see the <a 528 * href="http://www.twinsun.com/tz/tz-link.htm">TZ database</a>. 529 * @param timezoneId the timezone identifier or null to remove 530 * @rfc 5545 p.27-8 531 */ 532 public void setTimezoneId(String timezoneId) { 533 replace(TZID, timezoneId); 534 } 535 536 /** 537 * Gets the data type of the property's value (for example, "text" or 538 * "datetime"). 539 * @return the data type or null if not set 540 * @rfc 5545 p.29-50 541 */ 542 public ICalDataType getValue() { 543 String value = first(VALUE); 544 return (value == null) ? null : ICalDataType.get(value); 545 } 546 547 /** 548 * Sets the data type of the property's value (for example, "text" or 549 * "datetime"). 550 * @param value the data type or null to remove 551 * @rfc 5545 p.29-50 552 */ 553 public void setValue(ICalDataType value) { 554 replace(VALUE, (value == null) ? null : value.getName()); 555 } 556 557 /** 558 * Checks this parameters list for data consistency problems or deviations 559 * from the spec. These problems will not prevent the iCalendar object from 560 * being written to a data stream, but may prevent it from being parsed 561 * correctly by the consuming application. 562 * @return a list of warnings or an empty list if no problems were found 563 */ 564 public List<String> validate() { 565 List<String> warnings = new ArrayList<String>(0); 566 String message = "%s parameter has a non-standard value (\"%s\"). Standard values are: %s"; 567 568 String value = first(RSVP); 569 if (value != null && !value.equalsIgnoreCase("true") && !value.equalsIgnoreCase("false")) { 570 warnings.add(String.format(message, RSVP, value, "[TRUE, FALSE]")); 571 } 572 573 value = first(CUTYPE); 574 if (value != null && CalendarUserType.find(value) == null) { 575 warnings.add(String.format(message, CUTYPE, value, CalendarUserType.all())); 576 } 577 578 value = first(ENCODING); 579 if (value != null && Encoding.find(value) == null) { 580 warnings.add(String.format(message, ENCODING, value, Encoding.all())); 581 } 582 583 value = first(FBTYPE); 584 if (value != null && FreeBusyType.find(value) == null) { 585 warnings.add(String.format(message, FBTYPE, value, FreeBusyType.all())); 586 } 587 588 value = first(PARTSTAT); 589 if (value != null && ParticipationStatus.find(value) == null) { 590 warnings.add(String.format(message, PARTSTAT, value, ParticipationStatus.all())); 591 } 592 593 value = first(RANGE); 594 if (value != null && Range.find(value) == null) { 595 warnings.add(String.format(message, RANGE, value, Range.all())); 596 } 597 598 value = first(RELATED); 599 if (value != null && Related.find(value) == null) { 600 warnings.add(String.format(message, RELATED, value, Related.all())); 601 } 602 603 value = first(RELTYPE); 604 if (value != null && RelationshipType.find(value) == null) { 605 warnings.add(String.format(message, RELTYPE, value, RelationshipType.all())); 606 } 607 608 value = first(ROLE); 609 if (value != null && Role.find(value) == null) { 610 warnings.add(String.format(message, ROLE, value, Role.all())); 611 } 612 613 value = first(VALUE); 614 if (value != null && ICalDataType.find(value) == null) { 615 warnings.add(String.format(message, VALUE, value, ICalDataType.all())); 616 } 617 618 return warnings; 619 } 620 621 @Override 622 protected String sanitizeKey(String key) { 623 return (key == null) ? null : key.toUpperCase(); 624 } 625 }