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 }