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    }