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}