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