001package biweekly.parameter;
002
003import java.util.ArrayList;
004import java.util.Arrays;
005import java.util.List;
006
007import biweekly.ICalDataType;
008import biweekly.ICalVersion;
009import biweekly.Warning;
010import biweekly.property.FreeBusy;
011import biweekly.property.RecurrenceId;
012import biweekly.property.RelatedTo;
013import biweekly.property.Trigger;
014import biweekly.util.ListMultimap;
015
016/*
017 Copyright (c) 2013-2015, 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 CHARSET = "CHARSET"; //1.0 only
049        public static final String CUTYPE = "CUTYPE";
050        public static final String DELEGATED_FROM = "DELEGATED-FROM";
051        public static final String DELEGATED_TO = "DELEGATED-TO";
052        public static final String DIR = "DIR";
053        public static final String ENCODING = "ENCODING";
054        public static final String FMTTYPE = "FMTTYPE";
055        public static final String FBTYPE = "FBTYPE";
056        public static final String LANGUAGE = "LANGUAGE";
057        public static final String MEMBER = "MEMBER";
058        public static final String PARTSTAT = "PARTSTAT";
059        public static final String RANGE = "RANGE";
060        public static final String RELATED = "RELATED";
061        public static final String RELTYPE = "RELTYPE";
062        public static final String ROLE = "ROLE";
063        public static final String RSVP = "RSVP";
064        public static final String SENT_BY = "SENT-BY";
065        public static final String TYPE = "TYPE"; //1.0 only
066        public static final String TZID = "TZID";
067        public static final String VALUE = "VALUE";
068
069        /**
070         * Creates a parameters list.
071         */
072        public ICalParameters() {
073                super(0); //initialize map size to 0 because most properties don't use any parameters
074        }
075
076        /**
077         * Copies an existing parameters list.
078         * @param parameters the list to copy
079         */
080        public ICalParameters(ICalParameters parameters) {
081                super(parameters);
082        }
083
084        /**
085         * Gets a URI pointing to additional information about the entity
086         * represented by the property.
087         * @return the URI or null if not set
088         * @see <a href="http://tools.ietf.org/html/rfc5545#page-14">RFC 5545
089         * p.14-5</a>
090         */
091        public String getAltRepresentation() {
092                return first(ALTREP);
093        }
094
095        /**
096         * Sets a URI pointing to additional information about the entity
097         * represented by the property.
098         * @param uri the URI or null to remove
099         * @see <a href="http://tools.ietf.org/html/rfc5545#page-14">RFC 5545
100         * p.14-5</a>
101         */
102        public void setAltRepresentation(String uri) {
103                replace(ALTREP, uri);
104        }
105
106        /**
107         * Gets the character set that the property value is encoded in.
108         * @return the character set or null if not set
109         * @see <a href="http://www.imc.org/pdi/vcal-10.doc">vCal 1.0 p.16</a>
110         */
111        public String getCharset() {
112                return first(CHARSET);
113        }
114
115        /**
116         * Sets the character set that the property value is encoded in.
117         * @param charset the character set or null to remove
118         * @see <a href="http://www.imc.org/pdi/vcal-10.doc">vCal 1.0 p.16</a>
119         */
120        public void setCharset(String charset) {
121                replace(CHARSET, charset);
122        }
123
124        /**
125         * Gets the display name of a person.
126         * @return the display name (e.g. "John Doe") or null if not set
127         * @see <a href="http://tools.ietf.org/html/rfc5545#page-15">RFC 5545
128         * p.15-6</a>
129         */
130        public String getCommonName() {
131                return first(CN);
132        }
133
134        /**
135         * Sets the display name of a person.
136         * @param cn the display name (e.g. "John Doe") or null to remove
137         * @see <a href="http://tools.ietf.org/html/rfc5545#page-15">RFC 5545
138         * p.15-6</a>
139         */
140        public void setCommonName(String cn) {
141                replace(CN, cn);
142        }
143
144        /**
145         * Gets the type of user an attendee is (for example, an "individual" or a
146         * "room").
147         * @return the calendar user type or null if not set
148         * @see <a href="http://tools.ietf.org/html/rfc5545#page-16">RFC 5545
149         * p.16</a>
150         */
151        public CalendarUserType getCalendarUserType() {
152                String value = first(CUTYPE);
153                return (value == null) ? null : CalendarUserType.get(value);
154        }
155
156        /**
157         * Sets the type of user an attendee is (for example, an "individual" or a
158         * "room").
159         * @param cutype the calendar user type or null to remove
160         * @see <a href="http://tools.ietf.org/html/rfc5545#page-16">RFC 5545
161         * p.16</a>
162         */
163        public void setCalendarUserType(CalendarUserType cutype) {
164                replace(CUTYPE, (cutype == null) ? null : cutype.getValue());
165        }
166
167        /**
168         * Gets the people who have delegated their responsibility to an attendee.
169         * @return the delegators (typically email URIs, e.g.
170         * "mailto:janedoe@example.com")
171         * @see <a href="http://tools.ietf.org/html/rfc5545#page-17">RFC 5545
172         * p.17</a>
173         */
174        public List<String> getDelegatedFrom() {
175                return get(DELEGATED_FROM);
176        }
177
178        /**
179         * Adds a person who has delegated his or her responsibility to an attendee.
180         * @param uri the delegator (typically an email URI, e.g.
181         * "mailto:janedoe@example.com")
182         * @see <a href="http://tools.ietf.org/html/rfc5545#page-17">RFC 5545
183         * p.17</a>
184         */
185        public void addDelegatedFrom(String uri) {
186                put(DELEGATED_FROM, uri);
187        }
188
189        /**
190         * Removes a person who has delegated his or her responsibility to an
191         * attendee.
192         * @param uri the delegator to remove (typically an email URI, e.g.
193         * "mailto:janedoe@example.com")
194         * @see <a href="http://tools.ietf.org/html/rfc5545#page-17">RFC 5545
195         * p.17</a>
196         */
197        public void removeDelegatedFrom(String uri) {
198                remove(DELEGATED_FROM, uri);
199        }
200
201        /**
202         * Removes everyone who has delegated his or her responsibility to an
203         * attendee.
204         * @see <a href="http://tools.ietf.org/html/rfc5545#page-17">RFC 5545
205         * p.17</a>
206         */
207        public void removeDelegatedFrom() {
208                removeAll(DELEGATED_FROM);
209        }
210
211        /**
212         * Gets the people to which an attendee has delegated his or her
213         * responsibility.
214         * @return the delegatees (typically email URIs, e.g.
215         * "mailto:janedoe@example.com")
216         * @see <a href="http://tools.ietf.org/html/rfc5545#page-17">RFC 5545
217         * p.17-8</a>
218         */
219        public List<String> getDelegatedTo() {
220                return get(DELEGATED_TO);
221        }
222
223        /**
224         * Adds a person to which an attendee has delegated his or her
225         * responsibility.
226         * @param uri the delegatee (typically an email URI, e.g.
227         * "mailto:janedoe@example.com")
228         * @see <a href="http://tools.ietf.org/html/rfc5545#page-17">RFC 5545
229         * p.17-8</a>
230         */
231        public void addDelegatedTo(String uri) {
232                put(DELEGATED_TO, uri);
233        }
234
235        /**
236         * Removes a person to which an attendee has delegated his or her
237         * responsibility.
238         * @param uri the delegatee to remove (typically an email URI, e.g.
239         * "mailto:janedoe@example.com")
240         * @see <a href="http://tools.ietf.org/html/rfc5545#page-17">RFC 5545
241         * p.17-8</a>
242         */
243        public void removeDelegatedTo(String uri) {
244                remove(DELEGATED_TO, uri);
245        }
246
247        /**
248         * Removes everyone to which an attendee has delegated his or her
249         * responsibility.
250         * @see <a href="http://tools.ietf.org/html/rfc5545#page-17">RFC 5545
251         * p.17-8</a>
252         */
253        public void removeDelegatedTo() {
254                removeAll(DELEGATED_TO);
255        }
256
257        /**
258         * Gets a URI that contains additional information about the person.
259         * @return the URI (e.g. an LDAP URI) or null if not set
260         * @see <a href="http://tools.ietf.org/html/rfc5545#page-18">RFC 5545
261         * p.18</a>
262         */
263        public String getDirectoryEntry() {
264                return first(DIR);
265        }
266
267        /**
268         * Sets a URI that contains additional information about the person.
269         * @param uri the URI (e.g. an LDAP URI) or null to remove
270         * @see <a href="http://tools.ietf.org/html/rfc5545#page-18">RFC 5545
271         * p.18</a>
272         */
273        public void setDirectoryEntry(String uri) {
274                replace(DIR, uri);
275        }
276
277        /**
278         * Gets the encoding of the property value (for example, "base64").
279         * @return the encoding or null if not set
280         * @see <a href="http://tools.ietf.org/html/rfc5545#page-18">RFC 5545
281         * p.18-9</a>
282         */
283        public Encoding getEncoding() {
284                String value = first(ENCODING);
285                return (value == null) ? null : Encoding.get(value);
286        }
287
288        /**
289         * Sets the encoding of the property value (for example, "base64").
290         * @param encoding the encoding or null to remove
291         * @see <a href="http://tools.ietf.org/html/rfc5545#page-18">RFC 5545
292         * p.18-9</a>
293         */
294        public void setEncoding(Encoding encoding) {
295                replace(ENCODING, (encoding == null) ? null : encoding.getValue());
296        }
297
298        /**
299         * Gets the content-type of the property's value.
300         * @return the content type (e.g. "image/png") or null if not set
301         * @see <a href="http://tools.ietf.org/html/rfc5545#page-19">RFC 5545
302         * p.19-20</a>
303         */
304        public String getFormatType() {
305                return first(FMTTYPE);
306        }
307
308        /**
309         * Sets the content-type of the property's value.
310         * @param formatType the content type (e.g. "image/png") or null to remove
311         * @see <a href="http://tools.ietf.org/html/rfc5545#page-19">RFC 5545
312         * p.19-20</a>
313         */
314        public void setFormatType(String formatType) {
315                replace(FMTTYPE, formatType);
316        }
317
318        /**
319         * Gets the person's status over the time periods that are specified in a
320         * {@link FreeBusy} property (for example, "free" or "busy"). If not set,
321         * the user should be considered "busy".
322         * @return the type or null if not set
323         * @see <a href="http://tools.ietf.org/html/rfc5545#page-20">RFC 5545
324         * p.20</a>
325         */
326        public FreeBusyType getFreeBusyType() {
327                String value = first(FBTYPE);
328                return (value == null) ? null : FreeBusyType.get(value);
329        }
330
331        /**
332         * Sets the person's status over the time periods that are specified in a
333         * {@link FreeBusy} property (for example, "free" or "busy"). If not set,
334         * the user should be considered "busy".
335         * @param fbType the type or null to remove
336         * @see <a href="http://tools.ietf.org/html/rfc5545#page-20">RFC 5545
337         * p.20</a>
338         */
339        public void setFreeBusyType(FreeBusyType fbType) {
340                replace(FBTYPE, (fbType == null) ? null : fbType.getValue());
341        }
342
343        /**
344         * Gets the language that the property value is written in.
345         * @return the language (e.g. "en" for English) or null if not set
346         * @see <a href="http://tools.ietf.org/html/rfc5545#page-21">RFC 5545
347         * p.21</a>
348         */
349        public String getLanguage() {
350                return first(LANGUAGE);
351        }
352
353        /**
354         * Sets the language that the property value is written in.
355         * @param language the language (e.g. "en" for English) or null to remove
356         * @see <a href="http://tools.ietf.org/html/rfc5545#page-21">RFC 5545
357         * p.21</a>
358         */
359        public void setLanguage(String language) {
360                replace(LANGUAGE, language);
361        }
362
363        /**
364         * Adds a group that an attendee is a member of.
365         * @param uri the group URI (typically, an email address URI, e.g.
366         * "mailto:mailinglist@example.com")
367         * @see <a href="http://tools.ietf.org/html/rfc5545#page-21">RFC 5545
368         * p.21-2</a>
369         */
370        public void addMember(String uri) {
371                put(MEMBER, uri);
372        }
373
374        /**
375         * Gets the groups that an attendee is a member of.
376         * @return the group URIs (typically, these are email address URIs, e.g.
377         * "mailto:mailinglist@example.com")
378         * @see <a href="http://tools.ietf.org/html/rfc5545#page-21">RFC 5545
379         * p.21-2</a>
380         */
381        public List<String> getMembers() {
382                return get(MEMBER);
383        }
384
385        /**
386         * Removes a group that an attendee is a member of.
387         * @param uri the group URI to remove (typically, an email address URI, e.g.
388         * "mailto:mailinglist@example.com")
389         * @see <a href="http://tools.ietf.org/html/rfc5545#page-21">RFC 5545
390         * p.21-2</a>
391         */
392        public void removeMember(String uri) {
393                remove(MEMBER, uri);
394        }
395
396        /**
397         * Removes all groups that an attendee is a member of.
398         * @see <a href="http://tools.ietf.org/html/rfc5545#page-21">RFC 5545
399         * p.21-2</a>
400         */
401        public void removeMembers() {
402                removeAll(MEMBER);
403        }
404
405        /**
406         * Gets the effective range of recurrence instances from the instance
407         * specified by a {@link RecurrenceId} property.
408         * @return the range or null if not set
409         * @see <a href="http://tools.ietf.org/html/rfc5545#page-23">RFC 5545
410         * p.23-4</a>
411         */
412        public Range getRange() {
413                String value = first(RANGE);
414                return (value == null) ? null : Range.get(value);
415        }
416
417        /**
418         * Sets the effective range of recurrence instances from the instance
419         * specified by a {@link RecurrenceId} property.
420         * @param range the range or null to remove
421         * @see <a href="http://tools.ietf.org/html/rfc5545#page-23">RFC 5545
422         * p.23-4</a>
423         */
424        public void setRange(Range range) {
425                replace(RANGE, (range == null) ? null : range.getValue());
426        }
427
428        /**
429         * Gets the date-time field that the duration in a {@link Trigger} property
430         * is relative to.
431         * @return the field or null if not set
432         * @see <a href="http://tools.ietf.org/html/rfc5545#page-24">RFC 5545
433         * p.24</a>
434         */
435        public Related getRelated() {
436                String value = first(RELATED);
437                return (value == null) ? null : Related.get(value);
438        }
439
440        /**
441         * Sets the date-time field that the duration in a {@link Trigger} property
442         * is relative to.
443         * @param related the field or null to remove
444         * @see <a href="http://tools.ietf.org/html/rfc5545#page-24">RFC 5545
445         * p.24</a>
446         */
447        public void setRelated(Related related) {
448                replace(RELATED, (related == null) ? null : related.getValue());
449        }
450
451        /**
452         * Gets the relationship type of a {@link RelatedTo} property.
453         * @return the relationship type (e.g. "child") or null if not set
454         * @see <a href="http://tools.ietf.org/html/rfc5545#page-25">RFC 5545
455         * p.25</a>
456         */
457        public RelationshipType getRelationshipType() {
458                String value = first(RELTYPE);
459                return (value == null) ? null : RelationshipType.get(value);
460        }
461
462        /**
463         * Sets the relationship type of a {@link RelatedTo} property.
464         * @param relationshipType the relationship type (e.g. "child") or null to
465         * remove
466         * @see <a href="http://tools.ietf.org/html/rfc5545#page-25">RFC 5545
467         * p.25</a>
468         */
469        public void setRelationshipType(RelationshipType relationshipType) {
470                replace(RELTYPE, (relationshipType == null) ? null : relationshipType.getValue());
471        }
472
473        /**
474         * Gets a person that is acting on behalf of the person defined in the
475         * property.
476         * @return a URI representing the person (typically, an email URI, e.g.
477         * "mailto:janedoe@example.com") or null if not set
478         * @see <a href="http://tools.ietf.org/html/rfc5545#page-27">RFC 5545
479         * p.27</a>
480         */
481        public String getSentBy() {
482                return first(SENT_BY);
483        }
484
485        /**
486         * Sets a person that is acting on behalf of the person defined in the
487         * property.
488         * @param uri a URI representing the person (typically, an email URI, e.g.
489         * "mailto:janedoe@example.com") or null to remove
490         * @see <a href="http://tools.ietf.org/html/rfc5545#page-27">RFC 5545
491         * p.27</a>
492         */
493        public void setSentBy(String uri) {
494                replace(SENT_BY, uri);
495        }
496
497        /**
498         * Gets the TZID property, which defines the timezone that this property is
499         * formatted in. It is either the ID of the VTIMEZONE component which
500         * contains the timezone definition, or globally unique ID (if it starts
501         * with a "/" character).
502         * @return the timezone ID or null if not set
503         * @see <a href="http://tools.ietf.org/html/rfc5545#page-27">RFC 5545
504         * p.27-8</a>
505         */
506        public String getTimezoneId() {
507                return first(TZID);
508        }
509
510        /**
511         * Sets the TZID property, which defines the timezone that this property is
512         * formatted in. It is either the ID of the VTIMEZONE component which
513         * contains the timezone definition, or globally unique ID (if it starts
514         * with a "/" character).
515         * @param timezoneId the timezone identifier or null to remove
516         * @see <a href="http://tools.ietf.org/html/rfc5545#page-27">RFC 5545
517         * p.27-8</a>
518         */
519        public void setTimezoneId(String timezoneId) {
520                replace(TZID, timezoneId);
521        }
522
523        /**
524         * Gets the data type of the property's value (for example, "text" or
525         * "datetime").
526         * @return the data type or null if not set
527         * @see <a href="http://tools.ietf.org/html/rfc5545#page-29">RFC 5545
528         * p.29-50</a>
529         */
530        public ICalDataType getValue() {
531                String value = first(VALUE);
532                return (value == null) ? null : ICalDataType.get(value);
533        }
534
535        /**
536         * Sets the data type of the property's value (for example, "text" or
537         * "datetime").
538         * @param value the data type or null to remove
539         * @see <a href="http://tools.ietf.org/html/rfc5545#page-29">RFC 5545
540         * p.29-50</a>
541         */
542        public void setValue(ICalDataType value) {
543                replace(VALUE, (value == null) ? null : value.getName());
544        }
545
546        /**
547         * Checks this parameters list for data consistency problems or deviations
548         * from the spec. These problems will not prevent the iCalendar object from
549         * being written to a data stream, but may prevent it from being parsed
550         * correctly by the consuming application.
551         * @param version the version to validate against
552         * @return a list of warnings or an empty list if no problems were found
553         */
554        public List<Warning> validate(ICalVersion version) {
555                List<Warning> warnings = new ArrayList<Warning>(0);
556
557                final int nonStandardCode = 1, deprecated = 47;
558
559                String value = first(RSVP);
560                if (value != null) {
561                        value = value.toLowerCase();
562                        List<String> validValues = Arrays.asList("true", "false", "yes", "no");
563                        if (!validValues.contains(value)) {
564                                warnings.add(Warning.validate(nonStandardCode, RSVP, value, validValues));
565                        }
566                }
567
568                value = first(CUTYPE);
569                if (value != null && CalendarUserType.find(value) == null) {
570                        warnings.add(Warning.validate(nonStandardCode, CUTYPE, value, CalendarUserType.all()));
571                }
572
573                value = first(ENCODING);
574                if (value != null && Encoding.find(value) == null) {
575                        warnings.add(Warning.validate(nonStandardCode, ENCODING, value, Encoding.all()));
576                }
577
578                value = first(FBTYPE);
579                if (value != null && FreeBusyType.find(value) == null) {
580                        warnings.add(Warning.validate(nonStandardCode, FBTYPE, value, FreeBusyType.all()));
581                }
582
583                value = first(PARTSTAT);
584                if (value != null && ParticipationStatus.find(value) == null) {
585                        warnings.add(Warning.validate(nonStandardCode, PARTSTAT, value, ParticipationStatus.all()));
586                }
587
588                value = first(RANGE);
589                if (value != null) {
590                        Range range = Range.find(value);
591
592                        if (range == null) {
593                                warnings.add(Warning.validate(nonStandardCode, RANGE, value, Range.all()));
594                        }
595
596                        if (range == Range.THIS_AND_PRIOR && version == ICalVersion.V2_0) {
597                                warnings.add(Warning.validate(deprecated, RANGE, value));
598                        }
599                }
600
601                value = first(RELATED);
602                if (value != null && Related.find(value) == null) {
603                        warnings.add(Warning.validate(nonStandardCode, RELATED, value, Related.all()));
604                }
605
606                value = first(RELTYPE);
607                if (value != null && RelationshipType.find(value) == null) {
608                        warnings.add(Warning.validate(nonStandardCode, RELTYPE, value, RelationshipType.all()));
609                }
610
611                value = first(ROLE);
612                if (value != null && Role.find(value) == null) {
613                        warnings.add(Warning.validate(nonStandardCode, ROLE, value, Role.all()));
614                }
615
616                value = first(VALUE);
617                if (value != null && ICalDataType.find(value) == null) {
618                        warnings.add(Warning.validate(nonStandardCode, VALUE, value, ICalDataType.all()));
619                }
620
621                return warnings;
622        }
623
624        @Override
625        protected String sanitizeKey(String key) {
626                return (key == null) ? null : key.toUpperCase();
627        }
628}