001package biweekly.parameter;
002
003import java.util.ArrayList;
004import java.util.List;
005
006import biweekly.ICalDataType;
007import biweekly.Warning;
008import biweekly.component.VTimezone;
009import biweekly.property.FreeBusy;
010import biweekly.property.RecurrenceId;
011import biweekly.property.RelatedTo;
012import biweekly.property.TimezoneId;
013import biweekly.property.Trigger;
014import 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 */
045public 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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-14">RFC 5545 p.14-5</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-14">RFC 5545 p.14-5</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-15">RFC 5545 p.15-6</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-15">RFC 5545 p.15-6</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-16">RFC 5545 p.16</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-16">RFC 5545 p.16</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-17">RFC 5545 p.17</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-17">RFC 5545 p.17</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-17">RFC 5545 p.17</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-17">RFC 5545 p.17</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-17">RFC 5545 p.17-8</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-17">RFC 5545 p.17-8</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-17">RFC 5545 p.17-8</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-17">RFC 5545 p.17-8</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-18">RFC 5545 p.18</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-18">RFC 5545 p.18</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-18">RFC 5545 p.18-9</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-18">RFC 5545 p.18-9</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-19">RFC 5545 p.19-20</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-19">RFC 5545 p.19-20</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-20">RFC 5545 p.20</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-20">RFC 5545 p.20</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-21">RFC 5545 p.21</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-21">RFC 5545 p.21</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-21">RFC 5545 p.21-2</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-21">RFC 5545 p.21-2</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-21">RFC 5545 p.21-2</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-21">RFC 5545 p.21-2</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-22">RFC 5545 p.22-3</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-22">RFC 5545 p.22-3</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-23">RFC 5545 p.23-4</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-23">RFC 5545 p.23-4</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-24">RFC 5545 p.24</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-24">RFC 5545 p.24</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-25">RFC 5545 p.25</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-25">RFC 5545 p.25</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-25">RFC 5545 p.25-6</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-25">RFC 5545 p.25-6</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-26">RFC 5545 p.26-7</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-26">RFC 5545 p.26-7</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-27">RFC 5545 p.27</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-27">RFC 5545 p.27</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-27">RFC 5545 p.27-8</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-27">RFC 5545 p.27-8</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-29">RFC 5545 p.29-50</a>
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         * @see <a href="http://tools.ietf.org/html/rfc5545#page-29">RFC 5545 p.29-50</a>
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}