001package ezvcard.parameter;
002
003import java.nio.charset.Charset;
004import java.nio.charset.IllegalCharsetNameException;
005import java.nio.charset.UnsupportedCharsetException;
006import java.util.AbstractList;
007import java.util.ArrayList;
008import java.util.Arrays;
009import java.util.Collections;
010import java.util.EnumSet;
011import java.util.HashMap;
012import java.util.List;
013import java.util.Map;
014import java.util.Set;
015
016import com.github.mangstadt.vinnie.SyntaxStyle;
017import com.github.mangstadt.vinnie.validate.AllowedCharacters;
018import com.github.mangstadt.vinnie.validate.VObjectValidator;
019
020import ezvcard.Messages;
021import ezvcard.VCardDataType;
022import ezvcard.VCardVersion;
023import ezvcard.ValidationWarning;
024import ezvcard.property.Address;
025import ezvcard.property.ClientPidMap;
026import ezvcard.property.Email;
027import ezvcard.property.Note;
028import ezvcard.property.Organization;
029import ezvcard.property.Photo;
030import ezvcard.property.SortString;
031import ezvcard.property.Sound;
032import ezvcard.property.StructuredName;
033import ezvcard.util.GeoUri;
034import ezvcard.util.ListMultimap;
035
036/*
037 Copyright (c) 2012-2023, Michael Angstadt
038 All rights reserved.
039
040 Redistribution and use in source and binary forms, with or without
041 modification, are permitted provided that the following conditions are met: 
042
043 1. Redistributions of source code must retain the above copyright notice, this
044 list of conditions and the following disclaimer. 
045 2. Redistributions in binary form must reproduce the above copyright notice,
046 this list of conditions and the following disclaimer in the documentation
047 and/or other materials provided with the distribution. 
048
049 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
050 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
051 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
052 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
053 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
054 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
055 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
056 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
057 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
058 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
059
060 The views and conclusions contained in the software and documentation are those
061 of the authors and should not be interpreted as representing official policies, 
062 either expressed or implied, of the FreeBSD Project.
063 */
064
065/**
066 * Stores the parameters (also known as "sub types") that belong to a property.
067 * @author Michael Angstadt
068 */
069public class VCardParameters extends ListMultimap<String, String> {
070        /**
071         * <p>
072         * Used to specify that the property value is an alternative representation
073         * of another property value.
074         * </p>
075         * <p>
076         * In the example below, the first three {@link Note} properties have the
077         * same ALTID. This means that they each contain the same value, but in
078         * different forms. In this case, each property value is written in a
079         * different language. The other {@link Note} properties in the example have
080         * different (or absent) ALTID values, which means they are not associated
081         * with the top three.
082         * </p>
083         * 
084         * <pre>
085         * NOTE;ALTID=1;LANGUAGE=en:Hello world!
086         * NOTE;ALTID=1;LANGUAGE=fr:Bonjour tout le monde!
087         * NOTE;ALTID=1;LANGUAGE=es:¡Hola, mundo!
088         * NOTE;ALTID=2;LANGUAGE=de:Meine Lieblingsfarbe ist blau.
089         * NOTE;ALTID=2;LANGUAGE=en:My favorite color is blue.
090         * NOTE:This vCard will self-destruct in 5 seconds.
091         * </pre>
092         * 
093         * <p>
094         * <b>Supported versions:</b> {@code 4.0}
095         * </p>
096         * @see <a href="http://tools.ietf.org/html/rfc6350#page-18">RFC 6350
097         * p.18</a>
098         */
099        public static final String ALTID = "ALTID";
100
101        /**
102         * <p>
103         * Defines the type of calendar that is used in a date or date-time property
104         * value (for example, "gregorian").
105         * </p>
106         * <p>
107         * <b>Supported versions:</b> {@code 4.0}
108         * </p>
109         * @see <a href="http://tools.ietf.org/html/rfc6350#page-20">RFC 6350
110         * p.20</a>
111         */
112        public static final String CALSCALE = "CALSCALE";
113
114        /**
115         * <p>
116         * Defines the character set that the property value is encoded in (for
117         * example, "UTF-8"). Typically, this is only used in 2.1 vCards when the
118         * property value is encoded in quoted-printable encoding.
119         * </p>
120         * <p>
121         * <b>Supported versions:</b> {@code 2.1}
122         * </p>
123         * @see <a href="http://www.imc.org/pdi/vcard-21.doc">vCard 2.1 p.20</a>
124         */
125        public static final String CHARSET = "CHARSET";
126
127        /**
128         * <p>
129         * This parameter is used when the property value is encoded in a form other
130         * than plain text (for example, "base64").
131         * </p>
132         * <p>
133         * <b>Supported versions:</b> {@code 2.1, 3.0}
134         * </p>
135         * @see <a href="http://tools.ietf.org/html/rfc2426">RFC 2426</a>
136         * @see <a href="http://www.imc.org/pdi/vcard-21.doc">vCard 2.1 p.19</a>
137         */
138        public static final String ENCODING = "ENCODING";
139
140        /**
141         * <p>
142         * Used to associate global positioning information with the property. It
143         * can be used with the {@link Address} property.
144         * </p>
145         * <p>
146         * <b>Supported versions:</b> {@code 4.0}
147         * </p>
148         * @see <a href="http://tools.ietf.org/html/rfc6350#page-22">RFC 6350
149         * p.22</a>
150         */
151        public static final String GEO = "GEO";
152
153        /**
154         * <p>
155         * Defines the sorted position of this property when it is grouped together
156         * with other properties of the same type. Properties with low INDEX values
157         * are put at the beginning of the sorted list. Properties with high INDEX
158         * values are put at the end of the list.
159         * </p>
160         * <p>
161         * <b>Supported versions:</b> {@code 4.0}
162         * </p>
163         * @see <a href="https://tools.ietf.org/html/rfc6715#page-7">RFC 6715
164         * p.7</a>
165         */
166        public static final String INDEX = "INDEX";
167
168        /**
169         * <p>
170         * Used by the {@link Address} property to define a mailing label for the
171         * address.
172         * </p>
173         * <p>
174         * <b>Supported versions:</b> {@code 4.0}
175         * </p>
176         * @see <a href="http://tools.ietf.org/html/rfc6350#page-33">RFC 6350
177         * p.33</a>
178         */
179        public static final String LABEL = "LABEL";
180
181        /**
182         * <p>
183         * Defines the language that the property value is written in (for example,
184         * "en" for English").
185         * </p>
186         * <p>
187         * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
188         * </p>
189         * @see <a href="http://tools.ietf.org/html/rfc6350#page-16">RFC 6350
190         * p.16</a>
191         * @see <a href="http://tools.ietf.org/html/rfc2426#page-6">RFC 2426 p.6</a>
192         * @see <a href="http://www.imc.org/pdi/vcard-21.doc">vCard 2.1 p.20</a>
193         */
194        public static final String LANGUAGE = "LANGUAGE";
195
196        /**
197         * <p>
198         * Used to define the skill or interest level the person has towards the
199         * topic defined by the property (for example, "beginner"). Its value varies
200         * depending on the property.
201         * </p>
202         * <p>
203         * <b>Supported versions:</b> {@code 4.0}
204         * </p>
205         * @see <a href="https://tools.ietf.org/html/rfc6715#page-8">RFC 6715
206         * p.8</a>
207         */
208        public static final String LEVEL = "LEVEL";
209
210        /**
211         * <p>
212         * Used in properties that have a URL as a value, such as {@link Photo} and
213         * {@link Sound}. It defines the content type of the referenced resource
214         * (for example, "image/png" for a PNG image).
215         * </p>
216         * <p>
217         * <b>Supported versions:</b> {@code 4.0}
218         * </p>
219         * @see <a href="http://tools.ietf.org/html/rfc6350#page-20">RFC 6350
220         * p.20</a>
221         */
222        public static final String MEDIATYPE = "MEDIATYPE";
223
224        /**
225         * <p>
226         * Defines a property ID. PIDs can exist on any property where multiple
227         * instances are allowed (such as {@link Email} or {@link Address}, but not
228         * {@link StructuredName} because only 1 instance of this property is
229         * allowed per vCard).
230         * </p>
231         * <p>
232         * When used in conjunction with the {@link ClientPidMap} property, it
233         * allows an individual property instance to be uniquely identifiable. This
234         * feature is made use of when two different versions of the same vCard have
235         * to be merged together (called "synchronizing").
236         * </p>
237         * <p>
238         * <b>Supported versions:</b> {@code 4.0}
239         * </p>
240         * @see <a href="http://tools.ietf.org/html/rfc6350#page-19">RFC 6350
241         * p.19</a>
242         */
243        public static final String PID = "PID";
244
245        /**
246         * <p>
247         * Defines the preference value. The lower this number is, the more
248         * "preferred" the property instance is compared with other properties of
249         * the same type. If a property doesn't have a preference value, then it is
250         * considered the least preferred.
251         * </p>
252         * <p>
253         * In the vCard below, the {@link Address} on the second row is the most
254         * preferred because it has the lowest PREF value.
255         * </p>
256         * 
257         * <pre>
258         * ADR;TYPE=work;PREF=2:;;1600 Amphitheatre Parkway;Mountain View;CA;94043
259         * ADR;TYPE=work;PREF=1:;;One Microsoft Way;Redmond;WA;98052
260         * ADR;TYPE=home:;;123 Maple St;Hometown;KS;12345
261         * </pre>
262         * 
263         * <p>
264         * <b>Supported versions:</b> {@code 4.0}
265         * </p>
266         * @see <a href="http://tools.ietf.org/html/rfc6350#page-17">RFC 6350
267         * p.17</a>
268         */
269        public static final String PREF = "PREF";
270
271        /**
272         * <p>
273         * This parameter defines how the vCard should be sorted amongst other
274         * vCards. For example, this can be used if the person's last name (defined
275         * in the {@link StructuredName} property) starts with characters that
276         * should be ignored during sorting (such as "d'Aboville").
277         * </p>
278         * <p>
279         * This parameter can be used with the {@link StructuredName} and
280         * {@link Organization} properties. 2.1 and 3.0 vCards should use the
281         * {@link SortString} property instead.
282         * </p>
283         * <p>
284         * This parameter can be multi-valued. The first value is the primary sort
285         * keyword (such as the person's last name), the second value is the
286         * secondary sort keyword (such as the person's first name), etc.
287         * </p>
288         * <p>
289         * <b>Supported versions:</b> {@code 4.0}
290         * </p>
291         * @see <a href="http://tools.ietf.org/html/rfc6350#page-21">RFC 6350
292         * p.21</a>
293         */
294        public static final String SORT_AS = "SORT-AS";
295
296        /**
297         * <p>
298         * The meaning of this parameter varies depending on the property.
299         * </p>
300         * <p>
301         * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
302         * </p>
303         * @see <a href="http://tools.ietf.org/html/rfc6350#page-19">RFC 6350
304         * p.19</a>
305         * @see <a href="http://tools.ietf.org/html/rfc2426#page-6">RFC 2426 p.6</a>
306         * @see <a href="http://www.imc.org/pdi/vcard-21.doc">vCard 2.1</a>
307         */
308        public static final String TYPE = "TYPE";
309
310        /**
311         * <p>
312         * Used to associate timezone information with an {@link Address} property
313         * (for example, "America/New_York" to indicate that an address adheres to
314         * that timezone).
315         * </p>
316         * <p>
317         * <b>Supported versions:</b> {@code 4.0}
318         * </p>
319         * @see <a href="http://tools.ietf.org/html/rfc6350#page-22">RFC 6350
320         * p.22</a>
321         */
322        public static final String TZ = "TZ";
323
324        /**
325         * <p>
326         * Defines the data type of the property value (for example, "date" if the
327         * property value is a date without a time component). It is used if the
328         * property accepts multiple values that have different data types.
329         * </p>
330         * <p>
331         * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
332         * </p>
333         * @see <a href="http://tools.ietf.org/html/rfc6350#page-16">RFC 6350
334         * p.16</a>
335         * @see <a href="http://tools.ietf.org/html/rfc2426#page-6">RFC 2426 p.6</a>
336         * @see <a href="http://www.imc.org/pdi/vcard-21.doc">vCard 2.1 p.20</a>
337         */
338        public static final String VALUE = "VALUE";
339
340        private static final Map<String, Set<VCardVersion>> supportedVersions;
341        static {
342                Map<String, Set<VCardVersion>> m = new HashMap<>();
343                m.put(ALTID, EnumSet.of(VCardVersion.V4_0));
344                m.put(CALSCALE, EnumSet.of(VCardVersion.V4_0));
345                m.put(CHARSET, EnumSet.of(VCardVersion.V2_1));
346                m.put(GEO, EnumSet.of(VCardVersion.V4_0));
347                m.put(INDEX, EnumSet.of(VCardVersion.V4_0));
348
349                /*
350                 * Don't check LABEL because this is removed and converted to LABEL
351                 * properties for 2.1 and 3.0 vCards.
352                 */
353                //m.put(LABEL, EnumSet.of(VCardVersion.V4_0));
354
355                m.put(LEVEL, EnumSet.of(VCardVersion.V4_0));
356                m.put(MEDIATYPE, EnumSet.of(VCardVersion.V4_0));
357                m.put(PID, EnumSet.of(VCardVersion.V4_0));
358
359                /*
360                 * Don't check PREF because this is removed and converted to "TYPE=PREF"
361                 * for 2.1 and 3.0 vCards.
362                 */
363                //m.put(PREF, EnumSet.of(VCardVersion.V4_0));
364
365                m.put(SORT_AS, EnumSet.of(VCardVersion.V4_0));
366                m.put(TZ, EnumSet.of(VCardVersion.V4_0));
367
368                supportedVersions = Collections.unmodifiableMap(m);
369        }
370
371        /**
372         * Creates a list of parameters.
373         */
374        public VCardParameters() {
375                //empty
376        }
377
378        /**
379         * Creates a copy of an existing parameter list.
380         * @param orig the object to copy
381         */
382        public VCardParameters(VCardParameters orig) {
383                super(orig);
384        }
385
386        /**
387         * <p>
388         * Creates a parameter list that is backed by the given map. Any changes
389         * made to the given map will effect the parameter list and vice versa.
390         * </p>
391         * <p>
392         * Care must be taken to ensure that the given map's keys are all in
393         * uppercase.
394         * </p>
395         * <p>
396         * To avoid problems, it is highly recommended that the given map NOT be
397         * modified by anything other than this {@link VCardParameters} class after
398         * being passed into this constructor.
399         * </p>
400         * @param map the map
401         */
402        public VCardParameters(Map<String, List<String>> map) {
403                super(map);
404        }
405
406        /**
407         * <p>
408         * Gets the ALTID parameter value.
409         * </p>
410         * <p>
411         * This parameter is used to specify that the property value is an
412         * alternative representation of another property value.
413         * </p>
414         * <p>
415         * In the example below, the first three {@link Note} properties have the
416         * same ALTID. This means that they each contain the same value, but in
417         * different forms. In this case, each property value is written in a
418         * different language. The other {@link Note} properties in the example have
419         * different (or absent) ALTID values, which means they are not associated
420         * with the top three.
421         * </p>
422         * 
423         * <pre>
424         * NOTE;ALTID=1;LANGUAGE=en:Hello world!
425         * NOTE;ALTID=1;LANGUAGE=fr:Bonjour tout le monde!
426         * NOTE;ALTID=1;LANGUAGE=es:¡Hola, mundo!
427         * NOTE;ALTID=2;LANGUAGE=de:Meine Lieblingsfarbe ist blau.
428         * NOTE;ALTID=2;LANGUAGE=en:My favorite color is blue.
429         * NOTE:This vCard will self-destruct in 5 seconds.
430         * </pre>
431         * 
432         * <p>
433         * <b>Supported versions:</b> {@code 4.0}
434         * </p>
435         * @return the ALTID or null if not set
436         * @see <a href="http://tools.ietf.org/html/rfc6350#page-18">RFC 6350
437         * p.18</a>
438         */
439        public String getAltId() {
440                return first(ALTID);
441        }
442
443        /**
444         * <p>
445         * Sets the ALTID parameter value.
446         * </p>
447         * <p>
448         * This parameter is used to specify that the property value is an
449         * alternative representation of another property value.
450         * </p>
451         * <p>
452         * In the example below, the first three {@link Note} properties have the
453         * same ALTID. This means that they each contain the same value, but in
454         * different forms. In this case, each property value is written in a
455         * different language. The other {@link Note} properties in the example have
456         * different (or absent) ALTID values, which means they are not associated
457         * with the top three.
458         * </p>
459         * 
460         * <pre>
461         * NOTE;ALTID=1;LANGUAGE=en:Hello world!
462         * NOTE;ALTID=1;LANGUAGE=fr:Bonjour tout le monde!
463         * NOTE;ALTID=1;LANGUAGE=es:¡Hola, mundo!
464         * NOTE;ALTID=2;LANGUAGE=de:Meine Lieblingsfarbe ist blau.
465         * NOTE;ALTID=2;LANGUAGE=en:My favorite color is blue.
466         * NOTE:This vCard will self-destruct in 5 seconds.
467         * </pre>
468         * 
469         * <p>
470         * <b>Supported versions:</b> {@code 4.0}
471         * </p>
472         * @param altId the ALTID or null to remove
473         * @see <a href="http://tools.ietf.org/html/rfc6350#page-18">RFC 6350
474         * p.18</a>
475         */
476        public void setAltId(String altId) {
477                replace(ALTID, altId);
478        }
479
480        /**
481         * <p>
482         * Gets the CALSCALE (calendar scale) parameter value.
483         * </p>
484         * <p>
485         * This parameter defines the type of calendar that is used in a date or
486         * date-time property value (for example, "gregorian").
487         * </p>
488         * <p>
489         * <b>Supported versions:</b> {@code 4.0}
490         * </p>
491         * @return the type of calendar or null if not set
492         * @see <a href="http://tools.ietf.org/html/rfc6350#page-20">RFC 6350
493         * p.20</a>
494         */
495        public Calscale getCalscale() {
496                String value = first(CALSCALE);
497                return (value == null) ? null : Calscale.get(value);
498        }
499
500        /**
501         * <p>
502         * Sets the CALSCALE (calendar scale) parameter value.
503         * </p>
504         * <p>
505         * This parameter defines the type of calendar that is used in a date or
506         * date-time property value (for example, "gregorian").
507         * </p>
508         * <p>
509         * <b>Supported versions:</b> {@code 4.0}
510         * </p>
511         * @param calscale the type of calendar or null to remove
512         * @see <a href="http://tools.ietf.org/html/rfc6350#page-20">RFC 6350
513         * p.20</a>
514         */
515        public void setCalscale(Calscale calscale) {
516                replace(CALSCALE, (calscale == null) ? null : calscale.getValue());
517        }
518
519        /**
520         * <p>
521         * Gets the CHARSET (character set) parameter value.
522         * </p>
523         * <p>
524         * This parameter defines the character set that the property value is
525         * encoded in (for example, "UTF-8"). Typically, this is only used in 2.1
526         * vCards when the property value is encoded in quoted-printable encoding.
527         * </p>
528         * <p>
529         * <b>Supported versions:</b> {@code 2.1}
530         * </p>
531         * @return the character set or null if not set
532         * @see <a href="http://www.imc.org/pdi/vcard-21.doc">vCard 2.1 p.20</a>
533         */
534        public String getCharset() {
535                return first(CHARSET);
536        }
537
538        /**
539         * <p>
540         * Sets the CHARSET (character set) parameter value.
541         * </p>
542         * <p>
543         * This parameter defines the character set that the property value is
544         * encoded in (for example, "UTF-8"). Typically, this is only used in 2.1
545         * vCards when the property value is encoded in quoted-printable encoding.
546         * </p>
547         * <p>
548         * <b>Supported versions:</b> {@code 2.1}
549         * </p>
550         * @param charset the character set or null to remove
551         * @see <a href="http://www.imc.org/pdi/vcard-21.doc">vCard 2.1 p.20</a>
552         */
553        public void setCharset(String charset) {
554                replace(CHARSET, charset);
555        }
556
557        /**
558         * <p>
559         * Gets the ENCODING parameter value.
560         * </p>
561         * <p>
562         * This parameter is used when the property value is encoded in a form other
563         * than plain text (for example, "base64").
564         * </p>
565         * <p>
566         * <b>Supported versions:</b> {@code 2.1, 3.0}
567         * </p>
568         * @return the encoding or null if not set
569         * @see <a href="http://tools.ietf.org/html/rfc2426">RFC 2426</a>
570         * @see <a href="http://www.imc.org/pdi/vcard-21.doc">vCard 2.1 p.19</a>
571         */
572        public Encoding getEncoding() {
573                String value = first(ENCODING);
574                return (value == null) ? null : Encoding.get(value);
575        }
576
577        /**
578         * <p>
579         * Sets the ENCODING parameter value.
580         * </p>
581         * <p>
582         * This parameter is used when the property value is encoded in a form other
583         * than plain text (for example, "base64").
584         * </p>
585         * <p>
586         * <b>Supported versions:</b> {@code 2.1, 3.0}
587         * </p>
588         * @param encoding the encoding or null to remove
589         * @see <a href="http://tools.ietf.org/html/rfc2426">RFC 2426</a>
590         * @see <a href="http://www.imc.org/pdi/vcard-21.doc">vCard 2.1 p.19</a>
591         */
592        public void setEncoding(Encoding encoding) {
593                replace(ENCODING, (encoding == null) ? null : encoding.getValue());
594        }
595
596        /**
597         * <p>
598         * Gets the GEO parameter value.
599         * </p>
600         * <p>
601         * This parameter is used to associate global positioning information with
602         * the property. It can be used with the {@link Address} property.
603         * </p>
604         * <p>
605         * <b>Supported versions:</b> {@code 4.0}
606         * </p>
607         * @return the geo URI or null if not set
608         * @throws IllegalStateException if the parameter value is malformed and
609         * cannot be parsed into a geo URI. If this happens, you may use the
610         * {@link ListMultimap#get(Object) get()} method to retrieve its raw value.
611         * @see <a href="http://tools.ietf.org/html/rfc6350#page-22">RFC 6350
612         * p.22</a>
613         */
614        public GeoUri getGeo() {
615                String value = first(GEO);
616                if (value == null) {
617                        return null;
618                }
619
620                try {
621                        return GeoUri.parse(value);
622                } catch (IllegalArgumentException e) {
623                        throw new IllegalStateException(Messages.INSTANCE.getExceptionMessage(15, GEO), e);
624                }
625        }
626
627        /**
628         * <p>
629         * Sets the GEO parameter value.
630         * </p>
631         * <p>
632         * This parameter is used to associate global positioning information with
633         * the property. It can be used with the {@link Address} property.
634         * </p>
635         * <p>
636         * <b>Supported versions:</b> {@code 4.0}
637         * </p>
638         * @param uri the geo URI or null to remove
639         * @see <a href="http://tools.ietf.org/html/rfc6350#page-22">RFC 6350
640         * p.22</a>
641         */
642        public void setGeo(GeoUri uri) {
643                replace(GEO, (uri == null) ? null : uri.toString());
644        }
645
646        /**
647         * <p>
648         * Gets the INDEX parameter value.
649         * </p>
650         * <p>
651         * This parameter defines the sorted position of this property when it is
652         * grouped together with other properties of the same type. Properties with
653         * low INDEX values are put at the beginning of the sorted list. Properties
654         * with high INDEX values are put at the end of the list.
655         * </p>
656         * <p>
657         * <b>Supported versions:</b> {@code 4.0}
658         * </p>
659         * @return the index or null if not set
660         * @throws IllegalStateException if the parameter value cannot be parsed as
661         * an integer. If this happens, you may use the
662         * {@link ListMultimap#get(Object) get()} method to retrieve its raw value.
663         * @see <a href="https://tools.ietf.org/html/rfc6715#page-7">RFC 6715
664         * p.7</a>
665         */
666        public Integer getIndex() {
667                String index = first(INDEX);
668                if (index == null) {
669                        return null;
670                }
671
672                try {
673                        return Integer.valueOf(index);
674                } catch (NumberFormatException e) {
675                        throw new IllegalStateException(Messages.INSTANCE.getExceptionMessage(15, INDEX), e);
676                }
677        }
678
679        /**
680         * <p>
681         * Sets the INDEX parameter value.
682         * </p>
683         * <p>
684         * This parameter defines the sorted position of this property when it is
685         * grouped together with other properties of the same type. Properties with
686         * low INDEX values are put at the beginning of the sorted list. Properties
687         * with high INDEX values are put at the end of the list.
688         * </p>
689         * <p>
690         * <b>Supported versions:</b> {@code 4.0}
691         * </p>
692         * @param index the index or null to remove
693         * @see <a href="https://tools.ietf.org/html/rfc6715#page-7">RFC 6715
694         * p.7</a>
695         */
696        public void setIndex(Integer index) {
697                replace(INDEX, (index == null) ? null : index.toString());
698        }
699
700        /**
701         * <p>
702         * Gets the LABEL parameter value.
703         * </p>
704         * <p>
705         * This parameter is used by the {@link Address} property to define a
706         * mailing label for the address.
707         * </p>
708         * <p>
709         * <b>Supported versions:</b> {@code 4.0}
710         * </p>
711         * @return the label or null if not set
712         * @see <a href="http://tools.ietf.org/html/rfc6350#page-33">RFC 6350
713         * p.33</a>
714         */
715        public String getLabel() {
716                return first(LABEL);
717        }
718
719        /**
720         * <p>
721         * Sets the LABEL parameter value.
722         * </p>
723         * <p>
724         * This parameter is used by the {@link Address} property to define a
725         * mailing label for the address.
726         * </p>
727         * <p>
728         * <b>Supported versions:</b> {@code 4.0}
729         * </p>
730         * @param label the label or null to remove
731         * @see <a href="http://tools.ietf.org/html/rfc6350#page-33">RFC 6350
732         * p.33</a>
733         */
734        public void setLabel(String label) {
735                replace(LABEL, label);
736        }
737
738        /**
739         * <p>
740         * Gets the LANGUAGE parameter value.
741         * </p>
742         * <p>
743         * This parameter defines the language that the property value is written in
744         * (for example, "en" for English").
745         * </p>
746         * <p>
747         * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
748         * </p>
749         * @return the language or null if not set
750         * @see <a href="http://tools.ietf.org/html/rfc6350#page-16">RFC 6350
751         * p.16</a>
752         * @see <a href="http://tools.ietf.org/html/rfc2426#page-6">RFC 2426 p.6</a>
753         * @see <a href="http://www.imc.org/pdi/vcard-21.doc">vCard 2.1 p.20</a>
754         */
755        public String getLanguage() {
756                return first(LANGUAGE);
757        }
758
759        /**
760         * <p>
761         * Sets the LANGUAGE parameter value.
762         * </p>
763         * <p>
764         * This parameter defines the language that the property value is written in
765         * (for example, "en" for English").
766         * </p>
767         * <p>
768         * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
769         * </p>
770         * @param language the language or null to remove
771         * @see <a href="http://tools.ietf.org/html/rfc6350#page-16">RFC 6350
772         * p.16</a>
773         * @see <a href="http://tools.ietf.org/html/rfc2426#page-6">RFC 2426 p.6</a>
774         * @see <a href="http://www.imc.org/pdi/vcard-21.doc">vCard 2.1 p.20</a>
775         */
776        public void setLanguage(String language) {
777                replace(LANGUAGE, language);
778        }
779
780        /**
781         * <p>
782         * Gets the LEVEL parameter value.
783         * </p>
784         * <p>
785         * This parameter is used to define the skill or interest level the person
786         * has towards the topic defined by the property (for example, "beginner").
787         * Its value varies depending on the property.
788         * </p>
789         * <p>
790         * <b>Supported versions:</b> {@code 4.0}
791         * </p>
792         * @return the level or null if not set
793         * @see <a href="https://tools.ietf.org/html/rfc6715#page-8">RFC 6715
794         * p.8</a>
795         */
796        public String getLevel() {
797                return first(LEVEL);
798        }
799
800        /**
801         * <p>
802         * Sets the LEVEL parameter value.
803         * </p>
804         * <p>
805         * This parameter is used to define the skill or interest level the person
806         * has towards the topic defined by the property (for example, "beginner").
807         * Its value varies depending on the property.
808         * </p>
809         * <p>
810         * <b>Supported versions:</b> {@code 4.0}
811         * </p>
812         * @param level the level or null to remove
813         * @see <a href="https://tools.ietf.org/html/rfc6715#page-8">RFC 6715
814         * p.8</a>
815         */
816        public void setLevel(String level) {
817                replace(LEVEL, level);
818        }
819
820        /**
821         * <p>
822         * Gets the MEDIATYPE parameter value.
823         * </p>
824         * <p>
825         * This parameter is used in properties that have a URL as a value, such as
826         * {@link Photo} and {@link Sound}. It defines the content type of the
827         * referenced resource (for example, "image/png" for a PNG image).
828         * </p>
829         * <p>
830         * <b>Supported versions:</b> {@code 4.0}
831         * </p>
832         * @return the media type or null if not set
833         * @see <a href="http://tools.ietf.org/html/rfc6350#page-20">RFC 6350
834         * p.20</a>
835         */
836        public String getMediaType() {
837                return first(MEDIATYPE);
838        }
839
840        /**
841         * <p>
842         * Sets the MEDIATYPE parameter value.
843         * </p>
844         * <p>
845         * This parameter is used in properties that have a URL as a value, such as
846         * {@link Photo} and {@link Sound}. It defines the content type of the
847         * referenced resource (for example, "image/png" for a PNG image).
848         * </p>
849         * <p>
850         * <b>Supported versions:</b> {@code 4.0}
851         * </p>
852         * @param mediaType the media type or null to remove
853         * @see <a href="http://tools.ietf.org/html/rfc6350#page-20">RFC 6350
854         * p.20</a>
855         */
856        public void setMediaType(String mediaType) {
857                replace(MEDIATYPE, mediaType);
858        }
859
860        /**
861         * <p>
862         * Gets the PID (property ID) parameter values.
863         * </p>
864         * <p>
865         * PIDs can exist on any property where multiple instances are allowed (such
866         * as {@link Email} or {@link Address}, but not {@link StructuredName}
867         * because only 1 instance of this property is allowed per vCard).
868         * </p>
869         * <p>
870         * When used in conjunction with the {@link ClientPidMap} property, it
871         * allows an individual property instance to be uniquely identifiable. This
872         * feature is made use of when two different versions of the same vCard have
873         * to be merged together (called "synchronizing").
874         * </p>
875         * <p>
876         * Changes to the returned list will update the {@link VCardParameters}
877         * object, and vice versa.
878         * </p>
879         * <p>
880         * <b>Supported versions:</b> {@code 4.0}
881         * </p>
882         * @return the PIDs
883         * @throws IllegalStateException if one or more parameter values cannot be
884         * parsed as PIDs. If this happens, you may use the
885         * {@link ListMultimap#get(Object) get()} method to retrieve the raw values.
886         * @see <a href="http://tools.ietf.org/html/rfc6350#page-19">RFC 6350
887         * p.19</a>
888         */
889        public List<Pid> getPids() {
890                return new VCardParameterList<Pid>(PID) {
891                        @Override
892                        protected String _asString(Pid value) {
893                                return value.toString();
894                        }
895
896                        @Override
897                        protected Pid _asObject(String value) {
898                                return Pid.valueOf(value);
899                        }
900
901                        @Override
902                        protected IllegalStateException _exception(String value, Exception thrown) {
903                                return new IllegalStateException(Messages.INSTANCE.getExceptionMessage(15, PID), thrown);
904                        }
905                };
906        }
907
908        /**
909         * <p>
910         * Adds a PID (property ID) parameter value.
911         * </p>
912         * <p>
913         * PIDs can exist on any property where multiple instances are allowed (such
914         * as {@link Email} or {@link Address}, but not {@link StructuredName}
915         * because only 1 instance of this property is allowed per vCard).
916         * </p>
917         * <p>
918         * When used in conjunction with the {@link ClientPidMap} property, it
919         * allows an individual property instance to be uniquely identifiable. This
920         * feature is made use of when two different versions of the same vCard have
921         * to be merged together (called "synchronizing").
922         * </p>
923         * <p>
924         * <b>Supported versions:</b> {@code 4.0}
925         * </p>
926         * @param pid the PID to add
927         * @see <a href="http://tools.ietf.org/html/rfc6350#page-19">RFC 6350
928         * p.19</a>
929         */
930        public void addPid(Pid pid) {
931                put(PID, pid.toString());
932        }
933
934        /**
935         * <p>
936         * Removes a PID (property ID) parameter value.
937         * </p>
938         * <p>
939         * PIDs can exist on any property where multiple instances are allowed (such
940         * as {@link Email} or {@link Address}, but not {@link StructuredName}
941         * because only 1 instance of this property is allowed per vCard).
942         * </p>
943         * <p>
944         * When used in conjunction with the {@link ClientPidMap} property, it
945         * allows an individual property instance to be uniquely identifiable. This
946         * feature is made use of when two different versions of the same vCard have
947         * to be merged together (called "synchronizing").
948         * </p>
949         * <p>
950         * <b>Supported versions:</b> {@code 4.0}
951         * </p>
952         * @param pid the PID to remove
953         * @see <a href="http://tools.ietf.org/html/rfc6350#page-19">RFC 6350
954         * p.19</a>
955         */
956        public void removePid(Pid pid) {
957                String value = pid.toString();
958                remove(PID, value);
959        }
960
961        /**
962         * <p>
963         * Removes all PID (property ID) parameter values.
964         * </p>
965         * <p>
966         * PIDs can exist on any property where multiple instances are allowed (such
967         * as {@link Email} or {@link Address}, but not {@link StructuredName}
968         * because only 1 instance of this property is allowed per vCard).
969         * </p>
970         * <p>
971         * When used in conjunction with the {@link ClientPidMap} property, it
972         * allows an individual property instance to be uniquely identifiable. This
973         * feature is made use of when two different versions of the same vCard have
974         * to be merged together (called "synchronizing").
975         * </p>
976         * <p>
977         * <b>Supported versions:</b> {@code 4.0}
978         * </p>
979         * @see <a href="http://tools.ietf.org/html/rfc6350#page-19">RFC 6350
980         * p.19</a>
981         */
982        public void removePids() {
983                removeAll(PID);
984        }
985
986        /**
987         * <p>
988         * Gets the PREF (preference) parameter value.
989         * </p>
990         * <p>
991         * The lower this number is, the more "preferred" the property instance is
992         * compared with other properties of the same type. If a property doesn't
993         * have a preference value, then it is considered the least preferred.
994         * </p>
995         * <p>
996         * In the vCard below, the {@link Address} on the second row is the most
997         * preferred because it has the lowest PREF value.
998         * </p>
999         * 
1000         * <pre>
1001         * ADR;TYPE=work;PREF=2:;;1600 Amphitheatre Parkway;Mountain View;CA;94043
1002         * ADR;TYPE=work;PREF=1:;;One Microsoft Way;Redmond;WA;98052
1003         * ADR;TYPE=home:;;123 Maple St;Hometown;KS;12345
1004         * </pre>
1005         * 
1006         * <p>
1007         * <b>Supported versions:</b> {@code 4.0}
1008         * </p>
1009         * @return the preference value or null if not set
1010         * @throws IllegalStateException if the parameter value cannot be parsed as
1011         * an integer. If this happens, you may use the
1012         * {@link ListMultimap#get(Object) get()} method to retrieve its raw value.
1013         * @see <a href="http://tools.ietf.org/html/rfc6350#page-17">RFC 6350
1014         * p.17</a>
1015         */
1016        public Integer getPref() {
1017                String pref = first(PREF);
1018                if (pref == null) {
1019                        return null;
1020                }
1021
1022                try {
1023                        return Integer.valueOf(pref);
1024                } catch (NumberFormatException e) {
1025                        throw new IllegalStateException(Messages.INSTANCE.getExceptionMessage(15, PREF), e);
1026                }
1027        }
1028
1029        /**
1030         * <p>
1031         * Sets the PREF (preference) parameter value.
1032         * </p>
1033         * <p>
1034         * The lower this number is, the more "preferred" the property instance is
1035         * compared with other properties of the same type. If a property doesn't
1036         * have a preference value, then it is considered the least preferred.
1037         * </p>
1038         * <p>
1039         * In the vCard below, the {@link Address} on the second row is the most
1040         * preferred because it has the lowest PREF value.
1041         * </p>
1042         * 
1043         * <pre>
1044         * ADR;TYPE=work;PREF=2:;;1600 Amphitheatre Parkway;Mountain View;CA;94043
1045         * ADR;TYPE=work;PREF=1:;;One Microsoft Way;Redmond;WA;98052
1046         * ADR;TYPE=home:;;123 Maple St;Hometown;KS;12345
1047         * </pre>
1048         * 
1049         * <p>
1050         * <b>Supported versions:</b> {@code 4.0}
1051         * </p>
1052         * @param pref the preference value or null to remove
1053         * @see <a href="http://tools.ietf.org/html/rfc6350#page-17">RFC 6350
1054         * p.17</a>
1055         */
1056        public void setPref(Integer pref) {
1057                replace(PREF, (pref == null) ? null : pref.toString());
1058        }
1059
1060        /**
1061         * <p>
1062         * Gets the SORT-AS parameter values.
1063         * </p>
1064         * <p>
1065         * This parameter defines how the vCard should be sorted amongst other
1066         * vCards. For example, this can be used if the person's last name (defined
1067         * in the {@link StructuredName} property) starts with characters that
1068         * should be ignored during sorting (such as "d'Aboville").
1069         * </p>
1070         * <p>
1071         * This parameter can be used with the {@link StructuredName} and
1072         * {@link Organization} properties. 2.1 and 3.0 vCards should use the
1073         * {@link SortString} property instead.
1074         * </p>
1075         * <p>
1076         * This parameter can be multi-valued. The first value is the primary sort
1077         * keyword (such as the person's last name), the second value is the
1078         * secondary sort keyword (such as the person's first name), etc.
1079         * </p>
1080         * <p>
1081         * Changes to the returned list will update the {@link VCardParameters}
1082         * object, and vice versa.
1083         * </p>
1084         * <p>
1085         * <b>Supported versions:</b> {@code 4.0}
1086         * </p>
1087         * @return the sort strings
1088         * @see <a href="http://tools.ietf.org/html/rfc6350#page-21">RFC 6350
1089         * p.21</a>
1090         */
1091        public List<String> getSortAs() {
1092                return get(SORT_AS);
1093        }
1094
1095        /**
1096         * <p>
1097         * Sets the SORT-AS parameter values.
1098         * </p>
1099         * <p>
1100         * This parameter defines how the vCard should be sorted amongst other
1101         * vCards. For example, this can be used if the person's last name (defined
1102         * in the {@link StructuredName} property) starts with characters that
1103         * should be ignored during sorting (such as "d'Aboville").
1104         * </p>
1105         * <p>
1106         * This parameter can be used with the {@link StructuredName} and
1107         * {@link Organization} properties. 2.1 and 3.0 vCards should use the
1108         * {@link SortString} property instead.
1109         * </p>
1110         * <p>
1111         * This parameter can be multi-valued. The first value is the primary sort
1112         * keyword (such as the person's last name), the second value is the
1113         * secondary sort keyword (such as the person's first name), etc.
1114         * </p>
1115         * <p>
1116         * <b>Supported versions:</b> {@code 4.0}
1117         * </p>
1118         * @param sortStrings the sort strings or an empty parameter list to remove
1119         * @see <a href="http://tools.ietf.org/html/rfc6350#page-21">RFC 6350
1120         * p.21</a>
1121         */
1122        public void setSortAs(String... sortStrings) {
1123                removeAll(SORT_AS);
1124                putAll(SORT_AS, Arrays.asList(sortStrings));
1125        }
1126
1127        /**
1128         * <p>
1129         * Gets the TYPE parameter values.
1130         * </p>
1131         * <p>
1132         * The meaning of this parameter varies depending on the property.
1133         * </p>
1134         * <p>
1135         * Changes to the returned list will update the {@link VCardParameters}
1136         * object, and vice versa.
1137         * </p>
1138         * <p>
1139         * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
1140         * </p>
1141         * @return the types
1142         * @see <a href="http://tools.ietf.org/html/rfc6350#page-19">RFC 6350
1143         * p.19</a>
1144         * @see <a href="http://tools.ietf.org/html/rfc2426#page-6">RFC 2426 p.6</a>
1145         * @see <a href="http://www.imc.org/pdi/vcard-21.doc">vCard 2.1</a>
1146         */
1147        public List<String> getTypes() {
1148                return get(TYPE);
1149        }
1150
1151        /**
1152         * <p>
1153         * Gets the first TYPE parameter value.
1154         * </p>
1155         * <p>
1156         * The meaning of this parameter varies depending on the property.
1157         * </p>
1158         * <p>
1159         * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
1160         * </p>
1161         * @return the type or null if not set
1162         * @see <a href="http://tools.ietf.org/html/rfc6350#page-19">RFC 6350
1163         * p.19</a>
1164         * @see <a href="http://tools.ietf.org/html/rfc2426#page-6">RFC 2426 p.6</a>
1165         * @see <a href="http://www.imc.org/pdi/vcard-21.doc">vCard 2.1</a>
1166         */
1167        public String getType() {
1168                return first(TYPE);
1169        }
1170
1171        /**
1172         * <p>
1173         * Adds a TYPE parameter value.
1174         * </p>
1175         * <p>
1176         * The meaning of this parameter varies depending on the property.
1177         * </p>
1178         * <p>
1179         * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
1180         * </p>
1181         * @param type the type to add
1182         * @see <a href="http://tools.ietf.org/html/rfc6350#page-19">RFC 6350
1183         * p.19</a>
1184         * @see <a href="http://tools.ietf.org/html/rfc2426#page-6">RFC 2426 p.6</a>
1185         * @see <a href="http://www.imc.org/pdi/vcard-21.doc">vCard 2.1</a>
1186         */
1187        public void addType(String type) {
1188                put(TYPE, type);
1189        }
1190
1191        /**
1192         * <p>
1193         * Removes a TYPE parameter value.
1194         * </p>
1195         * <p>
1196         * The meaning of this parameter varies depending on the property.
1197         * </p>
1198         * <p>
1199         * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
1200         * </p>
1201         * @param type the type to remove
1202         * @see <a href="http://tools.ietf.org/html/rfc6350#page-19">RFC 6350
1203         * p.19</a>
1204         * @see <a href="http://tools.ietf.org/html/rfc2426#page-6">RFC 2426 p.6</a>
1205         * @see <a href="http://www.imc.org/pdi/vcard-21.doc">vCard 2.1</a>
1206         */
1207        public void removeType(String type) {
1208                remove(TYPE, type);
1209        }
1210
1211        /**
1212         * <p>
1213         * Sets the TYPE parameter value.
1214         * </p>
1215         * <p>
1216         * The meaning of this parameter varies depending on the property.
1217         * </p>
1218         * <p>
1219         * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
1220         * </p>
1221         * @param type the type or null to remove
1222         * @see <a href="http://tools.ietf.org/html/rfc6350#page-19">RFC 6350
1223         * p.19</a>
1224         * @see <a href="http://tools.ietf.org/html/rfc2426#page-6">RFC 2426 p.6</a>
1225         * @see <a href="http://www.imc.org/pdi/vcard-21.doc">vCard 2.1</a>
1226         */
1227        public void setType(String type) {
1228                replace(TYPE, type);
1229        }
1230
1231        /**
1232         * <p>
1233         * Gets the TZ (timezone) parameter value.
1234         * </p>
1235         * <p>
1236         * This parameter is used to associate timezone information with an
1237         * {@link Address} property (for example, "America/New_York" to indicate
1238         * that an address adheres to that timezone).
1239         * </p>
1240         * <p>
1241         * <b>Supported versions:</b> {@code 4.0}
1242         * </p>
1243         * @return the timezone or null if not set
1244         * @see <a href="http://tools.ietf.org/html/rfc6350#page-22">RFC 6350
1245         * p.22</a>
1246         */
1247        public String getTimezone() {
1248                return first(TZ);
1249        }
1250
1251        /**
1252         * <p>
1253         * Sets the TZ (timezone) parameter value.
1254         * </p>
1255         * <p>
1256         * This parameter is used to associate timezone information with an
1257         * {@link Address} property (for example, "America/New_York" to indicate
1258         * that an address adheres to that timezone).
1259         * </p>
1260         * <p>
1261         * <b>Supported versions:</b> {@code 4.0}
1262         * </p>
1263         * @param timezone the timezone or null to remove
1264         * @see <a href="http://tools.ietf.org/html/rfc6350#page-22">RFC 6350
1265         * p.22</a>
1266         */
1267        public void setTimezone(String timezone) {
1268                replace(TZ, timezone);
1269        }
1270
1271        /**
1272         * <p>
1273         * Gets the VALUE parameter value.
1274         * </p>
1275         * <p>
1276         * This parameter defines the data type of the property value (for example,
1277         * "date" if the property value is a date without a time component). It is
1278         * used if the property accepts multiple values that have different data
1279         * types.
1280         * </p>
1281         * <p>
1282         * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
1283         * </p>
1284         * @return the data type or null if not set
1285         * @see <a href="http://tools.ietf.org/html/rfc6350#page-16">RFC 6350
1286         * p.16</a>
1287         * @see <a href="http://tools.ietf.org/html/rfc2426#page-6">RFC 2426 p.6</a>
1288         * @see <a href="http://www.imc.org/pdi/vcard-21.doc">vCard 2.1 p.20</a>
1289         */
1290        public VCardDataType getValue() {
1291                String value = first(VALUE);
1292                return (value == null) ? null : VCardDataType.get(value);
1293        }
1294
1295        /**
1296         * <p>
1297         * Sets the VALUE parameter value.
1298         * </p>
1299         * <p>
1300         * This parameter defines the data type of the property value (for example,
1301         * "date" if the property value is a date without a time component). It is
1302         * used if the property accepts multiple values that have different data
1303         * types.
1304         * </p>
1305         * <p>
1306         * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
1307         * </p>
1308         * @param dataType the data type or null to remove
1309         * @see <a href="http://tools.ietf.org/html/rfc6350#page-16">RFC 6350
1310         * p.16</a>
1311         * @see <a href="http://tools.ietf.org/html/rfc2426#page-6">RFC 2426 p.6</a>
1312         * @see <a href="http://www.imc.org/pdi/vcard-21.doc">vCard 2.1 p.20</a>
1313         */
1314        public void setValue(VCardDataType dataType) {
1315                replace(VALUE, (dataType == null) ? null : dataType.getName());
1316        }
1317
1318        /**
1319         * <p>
1320         * Checks the parameters for data consistency problems or deviations from
1321         * the specification.
1322         * </p>
1323         * <p>
1324         * These problems will not prevent the vCard from being written to a data
1325         * stream*, but may prevent it from being parsed correctly by the consuming
1326         * application.
1327         * </p>
1328         * <p>
1329         * *With a few exceptions: One thing this method does is check for illegal
1330         * characters. There are certain characters that will break the vCard syntax
1331         * if written (such as a newline character in a parameter name). If one of
1332         * these characters is present, it WILL prevent the vCard from being
1333         * written.
1334         * </p>
1335         * @param version the vCard version to validate against
1336         * @return a list of warnings or an empty list if no problems were found
1337         */
1338        public List<ValidationWarning> validate(VCardVersion version) {
1339                List<ValidationWarning> warnings = new ArrayList<>(0);
1340
1341                /*
1342                 * Check for invalid characters in names and values.
1343                 */
1344                SyntaxStyle syntax = version.getSyntaxStyle();
1345                for (Map.Entry<String, List<String>> entry : this) {
1346                        String name = entry.getKey();
1347
1348                        /*
1349                         * Don't check LABEL parameter for 2.1 and 3.0 because this
1350                         * parameter is converted to a property in those versions.
1351                         */
1352                        if (version != VCardVersion.V4_0 && LABEL.equalsIgnoreCase(name)) {
1353                                continue;
1354                        }
1355
1356                        //check the parameter name
1357                        if (!VObjectValidator.validateParameterName(name, syntax, true)) {
1358                                if (syntax == SyntaxStyle.OLD) {
1359                                        AllowedCharacters notAllowed = VObjectValidator.allowedCharactersParameterName(syntax, true).flip();
1360                                        warnings.add(new ValidationWarning(30, name, notAllowed.toString(true)));
1361                                } else {
1362                                        warnings.add(new ValidationWarning(26, name));
1363                                }
1364                        }
1365
1366                        //check the parameter value(s)
1367                        List<String> values = entry.getValue();
1368                        for (String value : values) {
1369                                /*
1370                                 * Newlines are allowed in LABEL parameters, but are not allowed
1371                                 * by vobject, so remove them from the value before validating.
1372                                 */
1373                                if (LABEL.equalsIgnoreCase(name)) {
1374                                        value = value.replaceAll("\r\n|\r|\n", "");
1375                                }
1376
1377                                if (!VObjectValidator.validateParameterValue(value, syntax, false, true)) {
1378                                        AllowedCharacters notAllowed = VObjectValidator.allowedCharactersParameterValue(syntax, false, true).flip();
1379                                        int code = (syntax == SyntaxStyle.OLD) ? 31 : 25;
1380                                        warnings.add(new ValidationWarning(code, name, value, notAllowed.toString(true)));
1381                                }
1382                        }
1383                }
1384
1385                /*
1386                 * Check for invalid or unsupported values (e.g. "ENCODING=foo").
1387                 */
1388                {
1389                        final int nonStandardValueCode = 3;
1390                        final int unsupportedValueCode = 4;
1391
1392                        String value = first(CALSCALE);
1393                        if (value != null && Calscale.find(value) == null) {
1394                                warnings.add(new ValidationWarning(nonStandardValueCode, CALSCALE, value, Calscale.all()));
1395                        }
1396
1397                        value = first(ENCODING);
1398                        if (value != null) {
1399                                Encoding encoding = Encoding.find(value);
1400                                if (encoding == null) {
1401                                        warnings.add(new ValidationWarning(nonStandardValueCode, ENCODING, value, Encoding.all()));
1402                                } else if (!encoding.isSupportedBy(version)) {
1403                                        warnings.add(new ValidationWarning(unsupportedValueCode, ENCODING, value));
1404                                }
1405                        }
1406
1407                        value = first(VALUE);
1408                        if (value != null) {
1409                                VCardDataType dataType = VCardDataType.find(value);
1410                                if (dataType == null) {
1411                                        warnings.add(new ValidationWarning(nonStandardValueCode, VALUE, value, VCardDataType.all()));
1412                                } else if (!dataType.isSupportedBy(version)) {
1413                                        warnings.add(new ValidationWarning(unsupportedValueCode, VALUE, value));
1414                                }
1415                        }
1416                }
1417
1418                /*
1419                 * Check for parameters with malformed values.
1420                 */
1421                {
1422                        final int malformedValueCode = 5;
1423
1424                        try {
1425                                getGeo();
1426                        } catch (IllegalStateException e) {
1427                                warnings.add(new ValidationWarning(malformedValueCode, GEO, first(GEO)));
1428                        }
1429
1430                        try {
1431                                Integer index = getIndex();
1432                                if (index != null && index <= 0) {
1433                                        warnings.add(new ValidationWarning(28, index));
1434                                }
1435                        } catch (IllegalStateException e) {
1436                                warnings.add(new ValidationWarning(malformedValueCode, INDEX, first(INDEX)));
1437                        }
1438
1439                        List<String> pids = get(PID);
1440                        for (String pid : pids) {
1441                                if (!isPidValid(pid)) {
1442                                        warnings.add(new ValidationWarning(27, pid));
1443                                }
1444                        }
1445
1446                        try {
1447                                Integer pref = getPref();
1448                                if (pref != null && (pref < 1 || pref > 100)) {
1449                                        warnings.add(new ValidationWarning(29, pref));
1450                                }
1451                        } catch (IllegalStateException e) {
1452                                warnings.add(new ValidationWarning(malformedValueCode, PREF, first(PREF)));
1453                        }
1454                }
1455
1456                /*
1457                 * Check that each parameter is supported by the given vCard version.
1458                 */
1459                {
1460                        final int paramNotSupportedCode = 6;
1461
1462                        for (Map.Entry<String, Set<VCardVersion>> entry : supportedVersions.entrySet()) {
1463                                String name = entry.getKey();
1464                                String value = first(name);
1465                                if (value == null) {
1466                                        continue;
1467                                }
1468
1469                                Set<VCardVersion> versions = entry.getValue();
1470                                if (!versions.contains(version)) {
1471                                        warnings.add(new ValidationWarning(paramNotSupportedCode, name));
1472                                }
1473                        }
1474                }
1475
1476                /*
1477                 * Check that the CHARSET parameter has a character set that is
1478                 * supported by this JVM.
1479                 */
1480                {
1481                        final int invalidCharsetCode = 22;
1482
1483                        String charsetStr = getCharset();
1484                        if (charsetStr != null) {
1485                                try {
1486                                        Charset.forName(charsetStr);
1487                                } catch (IllegalCharsetNameException e) {
1488                                        warnings.add(new ValidationWarning(invalidCharsetCode, charsetStr));
1489                                } catch (UnsupportedCharsetException e) {
1490                                        warnings.add(new ValidationWarning(invalidCharsetCode, charsetStr));
1491                                }
1492                        }
1493                }
1494
1495                return warnings;
1496        }
1497
1498        private static boolean isPidValid(String pid) {
1499                boolean dotFound = false;
1500                for (int i = 0; i < pid.length(); i++) {
1501                        char c = pid.charAt(i);
1502
1503                        if (c == '.') {
1504                                if (i == 0 || i == pid.length() - 1) {
1505                                        return false;
1506                                }
1507                                if (dotFound) {
1508                                        return false;
1509                                }
1510                                dotFound = true;
1511                                continue;
1512                        }
1513
1514                        if (c >= '0' && c <= '9') {
1515                                continue;
1516                        }
1517
1518                        return false;
1519                }
1520
1521                return true;
1522        }
1523
1524        @Override
1525        protected String sanitizeKey(String key) {
1526                return (key == null) ? null : key.toUpperCase();
1527        }
1528
1529        @Override
1530        public int hashCode() {
1531                /*
1532                 * Remember: Keys are case-insensitive, key order does not matter, and
1533                 * value order does not matter
1534                 */
1535                final int prime = 31;
1536                int result = 1;
1537
1538                for (Map.Entry<String, List<String>> entry : this) {
1539                        String key = entry.getKey();
1540                        List<String> value = entry.getValue();
1541
1542                        int valueHash = 1;
1543                        for (String v : value) {
1544                                valueHash += v.toLowerCase().hashCode();
1545                        }
1546
1547                        int entryHash = 1;
1548                        entryHash += prime * entryHash + ((key == null) ? 0 : key.toLowerCase().hashCode());
1549                        entryHash += prime * entryHash + valueHash;
1550
1551                        result += entryHash;
1552                }
1553
1554                return result;
1555        }
1556
1557        /**
1558         * <p>
1559         * Determines whether the given object is logically equivalent to this list
1560         * of vCard parameters.
1561         * </p>
1562         * <p>
1563         * vCard parameters are case-insensitive. Also, the order in which they are
1564         * defined does not matter.
1565         * </p>
1566         * @param obj the object to compare to
1567         * @return true if the objects are equal, false if not
1568         */
1569        @Override
1570        public boolean equals(Object obj) {
1571                /*
1572                 * Remember: Keys are case-insensitive, key order does not matter, and
1573                 * value order does not matter
1574                 */
1575                if (this == obj) return true;
1576                if (obj == null) return false;
1577                if (getClass() != obj.getClass()) return false;
1578
1579                VCardParameters other = (VCardParameters) obj;
1580                if (size() != other.size()) return false;
1581
1582                for (Map.Entry<String, List<String>> entry : this) {
1583                        String key = entry.getKey();
1584                        List<String> value = entry.getValue();
1585                        List<String> otherValue = other.get(key);
1586
1587                        if (value.size() != otherValue.size()) {
1588                                return false;
1589                        }
1590
1591                        List<String> valueLower = new ArrayList<>(value.size());
1592                        for (String v : value) {
1593                                valueLower.add(v.toLowerCase());
1594                        }
1595                        Collections.sort(valueLower);
1596
1597                        List<String> otherValueLower = new ArrayList<>(otherValue.size());
1598                        for (String v : otherValue) {
1599                                otherValueLower.add(v.toLowerCase());
1600                        }
1601                        Collections.sort(otherValueLower);
1602
1603                        if (!valueLower.equals(otherValueLower)) {
1604                                return false;
1605                        }
1606                }
1607
1608                return true;
1609        }
1610
1611        /**
1612         * <p>
1613         * A list that converts the raw string values of a TYPE parameter to the
1614         * appropriate {@link VCardParameter} object that some parameters use.
1615         * </p>
1616         * <p>
1617         * This list is backed by the {@link VCardParameters} object. Any changes
1618         * made to the list will affect the {@link VCardParameters} object and vice
1619         * versa.
1620         * </p>
1621         * @param <T> the parameter class
1622         */
1623        public abstract class TypeParameterList<T extends VCardParameter> extends EnumParameterList<T> {
1624                public TypeParameterList() {
1625                        super(TYPE);
1626                }
1627        }
1628
1629        /**
1630         * <p>
1631         * A list that converts the raw string values of a parameter to the
1632         * appropriate {@link VCardParameter} object that some parameters use.
1633         * </p>
1634         * <p>
1635         * This list is backed by the {@link VCardParameters} object. Any changes
1636         * made to the list will affect the {@link VCardParameters} object and vice
1637         * versa.
1638         * </p>
1639         * @param <T> the parameter class
1640         */
1641        public abstract class EnumParameterList<T extends VCardParameter> extends VCardParameterList<T> {
1642                public EnumParameterList(String parameterName) {
1643                        super(parameterName);
1644                }
1645
1646                @Override
1647                protected String _asString(T value) {
1648                        return value.getValue();
1649                }
1650        }
1651
1652        /**
1653         * <p>
1654         * A list that converts the raw string values of a parameter to another kind
1655         * of value (for example, Integers).
1656         * </p>
1657         * <p>
1658         * This list is backed by the {@link VCardParameters} object. Any changes
1659         * made to the list will affect the {@link VCardParameters} object and vice
1660         * versa.
1661         * </p>
1662         * <p>
1663         * If a String value cannot be converted to the appropriate data type, an
1664         * {@link IllegalStateException} is thrown.
1665         * </p>
1666         */
1667        public abstract class VCardParameterList<T> extends AbstractList<T> {
1668                protected final String parameterName;
1669                protected final List<String> parameterValues;
1670
1671                /**
1672                 * @param parameterName the name of the parameter (case insensitive)
1673                 */
1674                public VCardParameterList(String parameterName) {
1675                        this.parameterName = parameterName;
1676                        parameterValues = VCardParameters.this.get(parameterName);
1677                }
1678
1679                @Override
1680                public void add(int index, T value) {
1681                        String valueStr = _asString(value);
1682                        parameterValues.add(index, valueStr);
1683                }
1684
1685                @Override
1686                public T remove(int index) {
1687                        String removed = parameterValues.remove(index);
1688                        return asObject(removed);
1689                }
1690
1691                @Override
1692                public T get(int index) {
1693                        String value = parameterValues.get(index);
1694                        return asObject(value);
1695                }
1696
1697                @Override
1698                public T set(int index, T value) {
1699                        String valueStr = _asString(value);
1700                        String replaced = parameterValues.set(index, valueStr);
1701                        return asObject(replaced);
1702                }
1703
1704                @Override
1705                public int size() {
1706                        return parameterValues.size();
1707                }
1708
1709                private T asObject(String value) {
1710                        try {
1711                                return _asObject(value);
1712                        } catch (Exception e) {
1713                                throw _exception(value, e);
1714                        }
1715                }
1716
1717                /**
1718                 * Converts the object to a String value for storing in the
1719                 * {@link VCardParameters} object.
1720                 * @param value the value
1721                 * @return the string value
1722                 */
1723                protected abstract String _asString(T value);
1724
1725                /**
1726                 * Converts a String value to its object form.
1727                 * @param value the string value
1728                 * @return the object
1729                 * @throws Exception if there is a problem parsing the string
1730                 */
1731                protected abstract T _asObject(String value) throws Exception;
1732
1733                /**
1734                 * Creates the exception that is thrown when the raw string value cannot
1735                 * be parsed into its object form.
1736                 * @param value the raw string value
1737                 * @param thrown the thrown exception
1738                 * @return the exception to throw
1739                 */
1740                protected IllegalStateException _exception(String value, Exception thrown) {
1741                        return new IllegalStateException(Messages.INSTANCE.getExceptionMessage(26, parameterName), thrown);
1742                }
1743        }
1744}