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