001 package ezvcard.parameter; 002 003 import java.nio.charset.Charset; 004 import java.nio.charset.IllegalCharsetNameException; 005 import java.nio.charset.UnsupportedCharsetException; 006 import java.util.ArrayList; 007 import java.util.Collections; 008 import java.util.EnumSet; 009 import java.util.HashMap; 010 import java.util.HashSet; 011 import java.util.List; 012 import java.util.Map; 013 import java.util.Set; 014 015 import ezvcard.VCardDataType; 016 import ezvcard.VCardVersion; 017 import ezvcard.Warning; 018 import ezvcard.property.Address; 019 import ezvcard.property.Organization; 020 import ezvcard.property.StructuredName; 021 import ezvcard.util.GeoUri; 022 import ezvcard.util.ListMultimap; 023 024 /* 025 Copyright (c) 2013, Michael Angstadt 026 All rights reserved. 027 028 Redistribution and use in source and binary forms, with or without 029 modification, are permitted provided that the following conditions are met: 030 031 1. Redistributions of source code must retain the above copyright notice, this 032 list of conditions and the following disclaimer. 033 2. Redistributions in binary form must reproduce the above copyright notice, 034 this list of conditions and the following disclaimer in the documentation 035 and/or other materials provided with the distribution. 036 037 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 038 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 039 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 040 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 041 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 042 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 043 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 044 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 045 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 046 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 047 048 The views and conclusions contained in the software and documentation are those 049 of the authors and should not be interpreted as representing official policies, 050 either expressed or implied, of the FreeBSD Project. 051 */ 052 053 /** 054 * Holds the parameters (aka "sub types") of a vCard property. 055 * @author Michael Angstadt 056 */ 057 public class VCardParameters extends ListMultimap<String, String> { 058 public static final String ALTID = "ALTID"; 059 public static final String CALSCALE = "CALSCALE"; 060 public static final String CHARSET = "CHARSET"; 061 public static final String ENCODING = "ENCODING"; 062 public static final String GEO = "GEO"; 063 public static final String INDEX = "INDEX"; 064 public static final String LABEL = "LABEL"; 065 public static final String LANGUAGE = "LANGUAGE"; 066 public static final String LEVEL = "LEVEL"; 067 public static final String MEDIATYPE = "MEDIATYPE"; 068 public static final String PID = "PID"; 069 public static final String PREF = "PREF"; 070 public static final String SORT_AS = "SORT-AS"; 071 public static final String TYPE = "TYPE"; 072 public static final String TZ = "TZ"; 073 public static final String VALUE = "VALUE"; 074 075 private static final Map<String, Set<VCardVersion>> supportedVersions; 076 static { 077 Map<String, Set<VCardVersion>> m = new HashMap<String, Set<VCardVersion>>(); 078 m.put(ALTID, EnumSet.of(VCardVersion.V4_0)); 079 m.put(CALSCALE, EnumSet.of(VCardVersion.V4_0)); 080 m.put(CHARSET, EnumSet.of(VCardVersion.V2_1)); 081 m.put(GEO, EnumSet.of(VCardVersion.V4_0)); 082 m.put(INDEX, EnumSet.of(VCardVersion.V4_0)); 083 084 //don't check LABEL because this is removed and converted to LABEL properties for 2.1 and 3.0 vCards 085 //m.put(LABEL, EnumSet.of(VCardVersion.V4_0)); 086 087 m.put(LEVEL, EnumSet.of(VCardVersion.V4_0)); 088 m.put(MEDIATYPE, EnumSet.of(VCardVersion.V4_0)); 089 m.put(PID, EnumSet.of(VCardVersion.V4_0)); 090 091 //don't check PREF because this is removed and converted to "TYPE=PREF" for 2.1 and 3.0 vCards 092 //m.put(PREF, EnumSet.of(VCardVersion.V4_0)); 093 094 m.put(SORT_AS, EnumSet.of(VCardVersion.V4_0)); 095 m.put(TZ, EnumSet.of(VCardVersion.V4_0)); 096 097 supportedVersions = Collections.unmodifiableMap(m); 098 } 099 100 /** 101 * Creates a list of parameters. 102 */ 103 public VCardParameters() { 104 //empty 105 } 106 107 /** 108 * Creates a copy of an existing parameter list. 109 * @param orig the object to copy 110 */ 111 public VCardParameters(VCardParameters orig) { 112 super(orig); 113 } 114 115 /** 116 * <p> 117 * Gets the ENCODING parameter. This is used when the property value is 118 * encoded in a form other than plain text. 119 * </p> 120 * <p> 121 * <b>Supported versions:</b> {@code 2.1, 3.0} 122 * </p> 123 * @return the encoding or null if not found 124 */ 125 public Encoding getEncoding() { 126 String value = first(ENCODING); 127 return (value == null) ? null : Encoding.get(value); 128 } 129 130 /** 131 * <p> 132 * Sets the ENCODING parameter. This is used when the property value is 133 * encoded in a form other than plain text. 134 * </p> 135 * <p> 136 * <b>Supported versions:</b> {@code 2.1, 3.0} 137 * </p> 138 * @param encoding the encoding or null to remove 139 */ 140 public void setEncoding(Encoding encoding) { 141 replace(ENCODING, (encoding == null) ? null : encoding.getValue()); 142 } 143 144 /** 145 * <p> 146 * Gets the VALUE parameter. This defines what kind of data type the 147 * property has, such as "text" or "URI". Only used in text-based vCards. 148 * </p> 149 * <p> 150 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0} 151 * </p> 152 * @return the value or null if not found 153 */ 154 public VCardDataType getValue() { 155 String value = first(VALUE); 156 return (value == null) ? null : VCardDataType.get(value); 157 } 158 159 /** 160 * <p> 161 * Sets the VALUE parameter. This defines what kind of data type the 162 * property has, such as "text" or "URI". Only used in text-based vCards. 163 * </p> 164 * <p> 165 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0} 166 * </p> 167 * @param value the value or null to remove 168 */ 169 public void setValue(VCardDataType value) { 170 replace(VALUE, (value == null) ? null : value.getName()); 171 } 172 173 /** 174 * <p> 175 * Removes the VALUE parameter. This defines what kind of data type the 176 * property has, such as "text" or "URI". Only used in text-based vCards. 177 * </p> 178 * <p> 179 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0} 180 * </p> 181 */ 182 public void removeValue() { 183 removeAll(VALUE); 184 } 185 186 /** 187 * <p> 188 * Gets the CHARSET parameter. 189 * </p> 190 * <p> 191 * <b>Supported versions:</b> {@code 2.1} 192 * </p> 193 * @return the value or null if not found 194 */ 195 public String getCharset() { 196 return first(CHARSET); 197 } 198 199 /** 200 * <p> 201 * Sets the CHARSET parameter. 202 * </p> 203 * <p> 204 * <b>Supported versions:</b> {@code 2.1} 205 * </p> 206 * @param charset the value or null to remove 207 */ 208 public void setCharset(String charset) { 209 replace(CHARSET, charset); 210 } 211 212 /** 213 * <p> 214 * Gets the LANGUAGE parameter. 215 * </p> 216 * <p> 217 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0} 218 * </p> 219 * @return the language (e.g. "en-US") or null if not set 220 * @see <a href="http://tools.ietf.org/html/rfc5646">RFC 5646</a> 221 */ 222 public String getLanguage() { 223 return first(LANGUAGE); 224 } 225 226 /** 227 * <p> 228 * Sets the LANGUAGE parameter. 229 * </p> 230 * <p> 231 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0} 232 * </p> 233 * @param language the language (e.g "en-US") or null to remove 234 * @see <a href="http://tools.ietf.org/html/rfc5646">RFC 5646</a> 235 */ 236 public void setLanguage(String language) { 237 replace(LANGUAGE, language); 238 } 239 240 /** 241 * <p> 242 * Gets the LABEL parameter. 243 * </p> 244 * <p> 245 * <b>Supported versions:</b> {@code 4.0} 246 * </p> 247 * @return the address label or null if not set 248 */ 249 public String getLabel() { 250 return first(LABEL); 251 } 252 253 /** 254 * <p> 255 * Sets the LABEL parameter. 256 * </p> 257 * <p> 258 * <b>Supported versions:</b> {@code 4.0} 259 * </p> 260 * @param label the address label or null to remove 261 */ 262 public void setLabel(String label) { 263 replace(LABEL, label); 264 } 265 266 /** 267 * <p> 268 * Gets the TZ parameter. 269 * </p> 270 * <p> 271 * <b>Supported versions:</b> {@code 4.0} 272 * </p> 273 * @return the timezone (e.g. "America/New_York") or null if not set 274 */ 275 public String getTimezone() { 276 return first(TZ); 277 } 278 279 /** 280 * <p> 281 * Sets the TZ parameter. 282 * </p> 283 * <p> 284 * <b>Supported versions:</b> {@code 4.0} 285 * </p> 286 * @param tz the timezone (e.g. "America/New_York") or null to remove 287 */ 288 public void setTimezone(String tz) { 289 replace(TZ, tz); 290 } 291 292 /** 293 * <p> 294 * Gets all TYPE parameters. 295 * </p> 296 * <p> 297 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0} 298 * </p> 299 * @return the values or empty set if not found 300 */ 301 public Set<String> getTypes() { 302 return new HashSet<String>(get(TYPE)); 303 } 304 305 /** 306 * <p> 307 * Adds a TYPE parameter. 308 * </p> 309 * <p> 310 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0} 311 * </p> 312 * @param type the value 313 */ 314 public void addType(String type) { 315 put(TYPE, type); 316 } 317 318 /** 319 * <p> 320 * Gets the first TYPE parameter. 321 * </p> 322 * <p> 323 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0} 324 * </p> 325 * @return the value or null if not found. 326 */ 327 public String getType() { 328 Set<String> types = getTypes(); 329 return types.isEmpty() ? null : types.iterator().next(); 330 } 331 332 /** 333 * <p> 334 * Sets the TYPE parameter. 335 * </p> 336 * <p> 337 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0} 338 * </p> 339 * @param type the value or null to remove 340 */ 341 public void setType(String type) { 342 replace(TYPE, type); 343 } 344 345 /** 346 * <p> 347 * Removes a TYPE parameter. 348 * </p> 349 * <p> 350 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0} 351 * </p> 352 * @param type the value to remove 353 */ 354 public void removeType(String type) { 355 remove(TYPE, type); 356 } 357 358 /** 359 * <p> 360 * Removes all TYPE parameters. 361 * </p> 362 * <p> 363 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0} 364 * </p> 365 */ 366 public void removeTypes() { 367 removeAll(TYPE); 368 } 369 370 /** 371 * <p> 372 * Gets the preference value. The lower the number, the more preferred this 373 * property instance is compared with other properties in the same vCard of 374 * the same type. If a property doesn't have a preference value, then it is 375 * considered the least preferred. 376 * </p> 377 * 378 * <p> 379 * In the vCard below, the address on the second row is the most preferred 380 * because it has the lowest PREF value. 381 * </p> 382 * 383 * <pre> 384 * ADR;TYPE=work;PREF=2: 385 * ADR;TYPE=work;PREF=1: 386 * ADR;TYPE=home: 387 * </pre> 388 * 389 * <p> 390 * Preference values must be numeric and must be between 1 and 100. 391 * </p> 392 * 393 * <p> 394 * <b>Supported versions:</b> {@code 4.0} 395 * </p> 396 * @throws IllegalStateException if the parameter value is malformed and 397 * cannot be parsed 398 * @return the preference value or null if it doesn't exist or null if it 399 * couldn't be parsed into a number 400 */ 401 public Integer getPref() { 402 String pref = first(PREF); 403 if (pref == null) { 404 return null; 405 } 406 407 try { 408 return Integer.valueOf(pref); 409 } catch (NumberFormatException e) { 410 throw new IllegalStateException(PREF + " parameter value is malformed and could not be parsed. Retrieve its raw text value instead.", e); 411 } 412 } 413 414 /** 415 * <p> 416 * Sets the preference value. The lower the number, the more preferred this 417 * property instance is compared with other properties in the same vCard of 418 * the same type. If a property doesn't have a preference value, then it is 419 * considered the least preferred. 420 * </p> 421 * 422 * <p> 423 * In the vCard below, the address on the second row is the most preferred 424 * because it has the lowest PREF value. 425 * </p> 426 * 427 * <pre> 428 * ADR;TYPE=work;PREF=2: 429 * ADR;TYPE=work;PREF=1: 430 * ADR;TYPE=home: 431 * </pre> 432 * 433 * <p> 434 * Preference values must be numeric and must be between 1 and 100. 435 * </p> 436 * 437 * <p> 438 * <b>Supported versions:</b> {@code 4.0} 439 * </p> 440 * @param pref the preference value or null to remove 441 * @throws IllegalArgumentException if the value is not between 1 and 100 442 */ 443 public void setPref(Integer pref) { 444 if (pref != null && (pref < 1 || pref > 100)) { 445 throw new IllegalArgumentException("Preference value must be between 1 and 100 inclusive."); 446 } 447 String value = (pref == null) ? null : pref.toString(); 448 replace(PREF, value); 449 } 450 451 /** 452 * <p> 453 * Gets the ALTID parameter value. This is used to specify alternative 454 * representations of the same type. 455 * </p> 456 * 457 * <p> 458 * For example, a vCard may contain multiple NOTE properties that each have 459 * the same ALTID. This means that each NOTE contains a different 460 * representation of the same information. In the example below, the first 461 * three NOTEs have the same ALTID. They each contain the same message, but 462 * each is written in a different language. The other NOTEs have different 463 * (or absent) ALTIDs, which means they are not associated with the top 464 * three. 465 * </p> 466 * 467 * <pre> 468 * NOTE;ALTID=1;LANGUAGE=en: Hello world! 469 * NOTE;ALTID=1;LANGUAGE=fr: Bonjour tout le monde! 470 * NOTE;ALTID=1;LANGUAGE=es: �Hola, mundo! 471 * NOTE;ALTID=2;LANGUAGE=de: Meine Lieblingsfarbe ist blau. 472 * NOTE;ALTID=2;LANGUAGE=en: My favorite color is blue. 473 * NOTE: This vCard will self-destruct in 5 seconds. 474 * </pre> 475 * 476 * <p> 477 * <b>Supported versions:</b> {@code 4.0} 478 * </p> 479 * @return the ALTID or null if it doesn't exist 480 */ 481 public String getAltId() { 482 return first(ALTID); 483 } 484 485 /** 486 * <p> 487 * Sets the ALTID parameter value. This is used to specify alternative 488 * representations of the same type. 489 * </p> 490 * 491 * <p> 492 * For example, a vCard may contain multiple NOTE properties that each have 493 * the same ALTID. This means that each NOTE contains a different 494 * representation of the same information. In the example below, the first 495 * three NOTEs have the same ALTID. They each contain the same message, but 496 * each is written in a different language. The other NOTEs have different 497 * (or absent) ALTIDs, which means they are not associated with the top 498 * three. 499 * </p> 500 * 501 * <pre> 502 * NOTE;ALTID=1;LANGUAGE=en: Hello world! 503 * NOTE;ALTID=1;LANGUAGE=fr: Bonjour tout le monde! 504 * NOTE;ALTID=1;LANGUAGE=es: �Hola, mundo! 505 * NOTE;ALTID=2;LANGUAGE=de: Meine Lieblingsfarbe ist blau. 506 * NOTE;ALTID=2;LANGUAGE=en: My favorite color is blue. 507 * NOTE: This vCard will self-destruct in 5 seconds. 508 * </pre> 509 * 510 * <p> 511 * <b>Supported versions:</b> {@code 4.0} 512 * </p> 513 * @param altId the ALTID or null to remove 514 */ 515 public void setAltId(String altId) { 516 replace(ALTID, altId); 517 } 518 519 /** 520 * <p> 521 * Gets the GEO parameter value. This is used to associate global 522 * positioning information with a vCard property. It can be used with the 523 * {@link Address} property. 524 * </p> 525 * <p> 526 * <b>Supported versions:</b> {@code 4.0} 527 * </p> 528 * @throws IllegalStateException if the parameter value is malformed and 529 * cannot be parsed 530 * @return the latitude (index 0) and longitude (index 1) or null if not 531 * present or null if the parameter value was in an incorrect format 532 */ 533 public double[] getGeo() { 534 String value = first(GEO); 535 if (value == null) { 536 return null; 537 } 538 539 try { 540 GeoUri geoUri = GeoUri.parse(value); 541 return new double[] { geoUri.getCoordA(), geoUri.getCoordB() }; 542 } catch (IllegalArgumentException e) { 543 throw new IllegalStateException(GEO + " parameter value is malformed and could not be parsed. Retrieve its raw text value instead.", e); 544 } 545 } 546 547 /** 548 * <p> 549 * Sets the GEO parameter value. This is used to associate global 550 * positioning information with a vCard property. It can be used with the 551 * {@link Address} property. 552 * </p> 553 * <p> 554 * <b>Supported versions:</b> {@code 4.0} 555 * </p> 556 * @param latitude the latitude 557 * @param longitude the longitude 558 */ 559 public void setGeo(double latitude, double longitude) { 560 GeoUri geoUri = new GeoUri.Builder(latitude, longitude).build(); 561 replace(GEO, geoUri.toString()); 562 } 563 564 /** 565 * <p> 566 * Gets the SORT-AS parameter value(s). This contains typically two string 567 * values which the vCard should be sorted by (family and given names). This 568 * is useful if the person's last name (defined in the N property) starts 569 * with characters that should be ignored during sorting. It can be used 570 * with the {@link StructuredName} and {@link Organization} properties. 571 * </p> 572 * <p> 573 * <b>Supported versions:</b> {@code 4.0} 574 * </p> 575 * @return the name(s) (e.g. { "Aboville", "Christine" } if the family name 576 * is "d'Aboville" and the given name is "Christine") or empty list of the 577 * parameter doesn't exist 578 */ 579 public List<String> getSortAs() { 580 return get(SORT_AS); 581 } 582 583 /** 584 * <p> 585 * Sets the SORT-AS parameter value(s). This is useful with the N property 586 * when the person's last name starts with characters that should be ignored 587 * during sorting. It can be used with the {@link StructuredName} and 588 * {@link Organization} properties. 589 * </p> 590 * <p> 591 * <b>Supported versions:</b> {@code 4.0} 592 * </p> 593 * @param names the names in the order they should be sorted in (e.g. 594 * ["Aboville", "Christine"] if the family name is "d'Aboville" and the 595 * given name is "Christine") or empty parameter list to remove 596 */ 597 public void setSortAs(String... names) { 598 removeAll(SORT_AS); 599 if (names != null && names.length > 0) { 600 for (String name : names) { 601 put(SORT_AS, name); 602 } 603 } 604 } 605 606 /** 607 * <p> 608 * Gets the type of calendar that is used for a date or date-time property 609 * value. 610 * </p> 611 * <p> 612 * <b>Supported versions:</b> {@code 4.0} 613 * </p> 614 * @return the type of calendar or null if not found 615 */ 616 public Calscale getCalscale() { 617 String value = first(CALSCALE); 618 return (value == null) ? null : Calscale.get(value); 619 } 620 621 /** 622 * <p> 623 * Sets the type of calendar that is used for a date or date-time property 624 * value. 625 * </p> 626 * <p> 627 * <b>Supported versions:</b> {@code 4.0} 628 * </p> 629 * @param value the type of calendar or null to remove 630 */ 631 public void setCalscale(Calscale value) { 632 replace(CALSCALE, (value == null) ? null : value.getValue()); 633 } 634 635 /** 636 * <p> 637 * Gets all PID parameter values. PIDs can exist on any property where 638 * multiple instances are allowed (such as EMAIL or ADR, but not N because 639 * only 1 instance of N is allowed). 640 * </p> 641 * <p> 642 * When used in conjunction with the CLIENTPIDMAP property, it allows an 643 * individual property instance to be uniquely identifiable. This feature is 644 * made use of when two different versions of the same vCard have to be 645 * merged together (called "synchronizing"). 646 * </p> 647 * <p> 648 * <b>Supported versions:</b> {@code 4.0} 649 * </p> 650 * @throws IllegalStateException if the parameter value is malformed and 651 * cannot be parsed 652 * @return the PID values or empty set if there are none. Index 0 is the 653 * local ID and index 1 is the ID used to reference the CLIENTPIDMAP 654 * property. Index 0 will never be null, but index 1 may be null. 655 */ 656 public List<Integer[]> getPids() { 657 List<String> values = get(PID); 658 List<Integer[]> pids = new ArrayList<Integer[]>(values.size()); 659 for (String value : values) { 660 String split[] = value.split("\\."); 661 try { 662 Integer localId = Integer.valueOf(split[0]); 663 Integer clientPidMapRef = (split.length > 1) ? Integer.valueOf(split[1]) : null; 664 pids.add(new Integer[] { localId, clientPidMapRef }); 665 } catch (NumberFormatException e) { 666 throw new IllegalStateException(PID + " parameter value is malformed and could not be parsed. Retrieve its raw text value instead.", e); 667 } 668 } 669 return pids; 670 } 671 672 /** 673 * <p> 674 * Adds a PID parameter value. PIDs can exist on any property where multiple 675 * instances are allowed (such as EMAIL or ADR, but not N because only 1 676 * instance of N is allowed). 677 * </p> 678 * <p> 679 * When used in conjunction with the CLIENTPIDMAP property, it allows an 680 * individual property instance to be uniquely identifiable. This feature is 681 * made use of when two different versions of the same vCard have to be 682 * merged together (called "synchronizing"). 683 * </p> 684 * <p> 685 * <b>Supported versions:</b> {@code 4.0} 686 * </p> 687 * @param localId the local ID 688 */ 689 public void addPid(int localId) { 690 put(PID, localId + ""); 691 } 692 693 /** 694 * <p> 695 * Adds a PID parameter value. PIDs can exist on any property where multiple 696 * instances are allowed (such as EMAIL or ADR, but not N because only 1 697 * instance of N is allowed). 698 * </p> 699 * <p> 700 * When used in conjunction with the CLIENTPIDMAP property, it allows an 701 * individual property instance to be uniquely identifiable. This feature is 702 * made use of when two different versions of the same vCard have to be 703 * merged together (called "synchronizing"). 704 * </p> 705 * <p> 706 * <b>Supported versions:</b> {@code 4.0} 707 * </p> 708 * @param localId the local ID 709 * @param clientPidMapRef the ID used to reference the property's globally 710 * unique identifier in the CLIENTPIDMAP property. 711 */ 712 public void addPid(int localId, int clientPidMapRef) { 713 put(PID, localId + "." + clientPidMapRef); 714 } 715 716 /** 717 * <p> 718 * Removes all PID values. 719 * </p> 720 * <p> 721 * <b>Supported versions:</b> {@code 4.0} 722 * </p> 723 */ 724 public void removePids() { 725 removeAll(PID); 726 } 727 728 /** 729 * <p> 730 * Gets the MEDIATYPE parameter. This is used in properties that have a URL 731 * as a value, such as PHOTO and SOUND. It defines the content type of the 732 * referenced resource. 733 * </p> 734 * <p> 735 * <b>Supported versions:</b> {@code 4.0} 736 * </p> 737 * @return the media type (e.g. "image/jpeg") or null if it doesn't exist 738 */ 739 public String getMediaType() { 740 return first(MEDIATYPE); 741 } 742 743 /** 744 * <p> 745 * Sets the MEDIATYPE parameter. This is used in properties that have a URL 746 * as a value, such as PHOTO and SOUND. It defines the content type of the 747 * referenced resource. 748 * </p> 749 * <p> 750 * <b>Supported versions:</b> {@code 4.0} 751 * </p> 752 * @param mediaType the media type (e.g. "image/jpeg") or null to remove 753 */ 754 public void setMediaType(String mediaType) { 755 replace(MEDIATYPE, mediaType); 756 } 757 758 /** 759 * <p> 760 * Gets the LEVEL parameter. This is used to define the level of skill or 761 * level of interest the person has towards something. 762 * </p> 763 * <p> 764 * <b>Supported versions:</b> {@code 4.0} 765 * </p> 766 * @return the level (e.g. "beginner") or null if not found 767 * @see <a href="http://tools.ietf.org/html/rfc6715">RFC 6715</a> 768 */ 769 public String getLevel() { 770 return first(LEVEL); 771 } 772 773 /** 774 * <p> 775 * Sets the LEVEL parameter. This is used to define the level of skill or 776 * level of interest the person has towards something. 777 * </p> 778 * <p> 779 * <b>Supported versions:</b> {@code 4.0} 780 * </p> 781 * @param level the level (e.g. "beginner") or null to remove 782 * @see <a href="http://tools.ietf.org/html/rfc6715">RFC 6715</a> 783 */ 784 public void setLevel(String level) { 785 replace(LEVEL, level); 786 } 787 788 /** 789 * <p> 790 * Gets the sorted position of this property when it is grouped together 791 * with other properties of the same type. Properties with low index values 792 * are put at the beginning of the sorted list and properties with high 793 * index values are put at the end of the list. 794 * </p> 795 * 796 * <p> 797 * <b>Supported versions:</b> {@code 4.0} 798 * </p> 799 * @throws IllegalStateException if the parameter value is malformed and 800 * cannot be parsed 801 * @return the INDEX value or null if it doesn't exist or null if it 802 * couldn't be parsed into a number 803 * @see <a href="http://tools.ietf.org/html/rfc6715">RFC 6715</a> 804 */ 805 public Integer getIndex() { 806 String index = first(INDEX); 807 if (index == null) { 808 return null; 809 } 810 811 try { 812 return Integer.valueOf(index); 813 } catch (NumberFormatException e) { 814 throw new IllegalStateException(INDEX + " parameter value is malformed and could not be parsed. Retrieve its raw text value instead.", e); 815 } 816 } 817 818 /** 819 * <p> 820 * Sets the sorted position of this property when it is grouped together 821 * with other properties of the same type. Properties with low index values 822 * are put at the beginning of the sorted list and properties with high 823 * index values are put at the end of the list. 824 * </p> 825 * 826 * <p> 827 * <b>Supported versions:</b> {@code 4.0} 828 * </p> 829 * @param index the INDEX value (must be greater than 0) or null to remove 830 * @see <a href="http://tools.ietf.org/html/rfc6715">RFC 6715</a> 831 * @throws IllegalArgumentException if the value is not greater than 0 832 */ 833 public void setIndex(Integer index) { 834 if (index != null && index <= 0) { 835 throw new IllegalArgumentException("Index value must be greater than 0."); 836 } 837 String value = (index == null) ? null : index.toString(); 838 replace(INDEX, value); 839 } 840 841 /** 842 * Checks this parameters list for data consistency problems or deviations 843 * from the spec. These problems will not prevent the vCard from being 844 * written to a data stream, but may prevent it from being parsed correctly 845 * by the consuming application. 846 * @param version the vCard version to validate against 847 * @return a list of warnings or an empty list if no problems were found 848 */ 849 public List<Warning> validate(VCardVersion version) { 850 List<Warning> warnings = new ArrayList<Warning>(0); 851 852 { 853 int nonStandardCode = 3; 854 int valueNotSupportedCode = 4; 855 856 String value = first(CALSCALE); 857 if (value != null && Calscale.find(value) == null) { 858 warnings.add(new Warning(nonStandardCode, CALSCALE, value, Calscale.all())); 859 } 860 861 value = first(ENCODING); 862 if (value != null) { 863 Encoding encoding = Encoding.find(value); 864 if (encoding == null) { 865 warnings.add(new Warning(nonStandardCode, ENCODING, value, Encoding.all())); 866 } else if (!encoding.isSupported(version)) { 867 warnings.add(new Warning(valueNotSupportedCode, ENCODING, value)); 868 } 869 } 870 871 value = first(VALUE); 872 if (value != null) { 873 VCardDataType dataType = VCardDataType.find(value); 874 if (dataType == null) { 875 warnings.add(new Warning(nonStandardCode, VALUE, value, VCardDataType.all())); 876 } else if (!dataType.isSupported(version)) { 877 warnings.add(new Warning(valueNotSupportedCode, VALUE, value)); 878 } 879 } 880 } 881 882 { 883 int malformedCode = 5; 884 885 try { 886 getGeo(); 887 } catch (IllegalStateException e) { 888 warnings.add(new Warning(malformedCode, GEO, first(GEO))); 889 } 890 891 try { 892 getIndex(); 893 } catch (IllegalStateException e) { 894 warnings.add(new Warning(malformedCode, INDEX, first(INDEX))); 895 } 896 897 try { 898 getPids(); 899 } catch (IllegalStateException e) { 900 warnings.add(new Warning(malformedCode, PID, first(PID))); 901 } 902 903 try { 904 getPref(); 905 } catch (IllegalStateException e) { 906 warnings.add(new Warning(malformedCode, PREF, first(PREF))); 907 } 908 } 909 910 { 911 int paramNotSupportedCode = 6; 912 for (Map.Entry<String, Set<VCardVersion>> entry : supportedVersions.entrySet()) { 913 String name = entry.getKey(); 914 String value = first(name); 915 if (value == null) { 916 continue; 917 } 918 919 Set<VCardVersion> versions = entry.getValue(); 920 if (!versions.contains(version)) { 921 warnings.add(new Warning(paramNotSupportedCode, name)); 922 } 923 } 924 } 925 926 { 927 int invalidCharsetCode = 22; 928 String charsetStr = getCharset(); 929 if (charsetStr != null) { 930 try { 931 Charset.forName(charsetStr); 932 } catch (IllegalCharsetNameException e) { 933 warnings.add(new Warning(invalidCharsetCode, charsetStr)); 934 } catch (UnsupportedCharsetException e) { 935 warnings.add(new Warning(invalidCharsetCode, charsetStr)); 936 } 937 } 938 } 939 940 return warnings; 941 } 942 943 @Override 944 protected String sanitizeKey(String key) { 945 return (key == null) ? null : key.toUpperCase(); 946 } 947 }