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    }