001 package ezvcard; 002 003 import java.util.ArrayList; 004 import java.util.HashSet; 005 import java.util.List; 006 import java.util.Set; 007 008 import ezvcard.parameters.CalscaleParameter; 009 import ezvcard.parameters.EncodingParameter; 010 import ezvcard.parameters.LevelParameter; 011 import ezvcard.parameters.TypeParameter; 012 import ezvcard.parameters.ValueParameter; 013 import ezvcard.util.GeoUri; 014 import ezvcard.util.ListMultimap; 015 016 /* 017 Copyright (c) 2012, Michael Angstadt 018 All rights reserved. 019 020 Redistribution and use in source and binary forms, with or without 021 modification, are permitted provided that the following conditions are met: 022 023 1. Redistributions of source code must retain the above copyright notice, this 024 list of conditions and the following disclaimer. 025 2. Redistributions in binary form must reproduce the above copyright notice, 026 this list of conditions and the following disclaimer in the documentation 027 and/or other materials provided with the distribution. 028 029 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 030 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 031 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 032 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 033 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 034 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 035 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 036 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 037 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 038 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 039 040 The views and conclusions contained in the software and documentation are those 041 of the authors and should not be interpreted as representing official policies, 042 either expressed or implied, of the FreeBSD Project. 043 */ 044 045 /** 046 * Holds the parameters (aka "sub types") of a vCard Type. 047 * @author Michael Angstadt 048 */ 049 public class VCardSubTypes { 050 private final ListMultimap<String, String> subTypes; 051 052 public VCardSubTypes() { 053 subTypes = new ListMultimap<String, String>(); 054 } 055 056 /** 057 * Copy constructor. 058 * @param orig the object to copy 059 */ 060 public VCardSubTypes(VCardSubTypes orig) { 061 subTypes = new ListMultimap<String, String>(orig.subTypes); 062 } 063 064 /** 065 * Adds a value to a Sub Type. 066 * @param name the Sub Type name 067 * @param value the value to add 068 */ 069 public void put(String name, String value) { 070 subTypes.put(name.toUpperCase(), value); 071 } 072 073 /** 074 * Adds a value to a Sub Type, replacing all existing values that the Sub 075 * Type has. 076 * @param name the Sub Type name 077 * @param value the values to replace all existing values with 078 * @return the values of the Sub Type that were replaced 079 */ 080 public List<String> replace(String name, String value) { 081 List<String> replaced = removeAll(name); 082 if (value != null) { 083 put(name, value); 084 } 085 return replaced; 086 } 087 088 /** 089 * Removes a Sub Type. 090 * @param name the Sub Type name 091 * @return the values of the Sub Type that were removed 092 */ 093 public List<String> removeAll(String name) { 094 return subTypes.remove(name.toUpperCase()); 095 } 096 097 /** 098 * Removes a value from a Sub Type. 099 * @param name the Sub Type name 100 * @param value the value to remove 101 */ 102 public void remove(String name, String value) { 103 subTypes.remove(name.toUpperCase(), value); 104 } 105 106 /** 107 * Gets the values of a Sub Type 108 * @param name the Sub Type name 109 * @return the values or an empty list if the Sub Type doesn't exist 110 */ 111 public List<String> get(String name) { 112 return subTypes.get(name.toUpperCase()); 113 } 114 115 /** 116 * Gets the first value of a Sub Type. 117 * @param name the Sub Type name 118 * @return the first value or null if the Sub Type doesn't exist 119 */ 120 public String getFirst(String name) { 121 List<String> list = get(name); 122 return list.isEmpty() ? null : list.get(0); 123 } 124 125 /** 126 * Gets the names of all the Sub Types. 127 * @return the names of all the Sub Types or an empty set if there are no 128 * Sub Types 129 */ 130 public Set<String> getNames() { 131 return subTypes.keySet(); 132 } 133 134 /** 135 * Gets the object used to store the Sub Types. 136 * @return the object used to store the Sub Types 137 */ 138 public ListMultimap<String, String> getMultimap() { 139 return subTypes; 140 } 141 142 /** 143 * Gets the ENCODING sub type. This is used when the type value is encoded 144 * in a form other than plain text. 145 * <p> 146 * vCard versions: 2.1, 3.0 147 * </p> 148 * @return the encoding or null if not found 149 */ 150 public EncodingParameter getEncoding() { 151 String value = getFirst(EncodingParameter.NAME); 152 if (value == null) { 153 return null; 154 } 155 EncodingParameter encoding = EncodingParameter.valueOf(value); 156 if (encoding == null) { 157 encoding = new EncodingParameter(value); 158 } 159 return encoding; 160 } 161 162 /** 163 * Sets the ENCODING sub type. This is used when the type value is encoded 164 * in a form other than plain text. 165 * <p> 166 * vCard versions: 2.1, 3.0 167 * </p> 168 * @param encoding the encoding or null to remove 169 */ 170 public void setEncoding(EncodingParameter encoding) { 171 replace(EncodingParameter.NAME, (encoding == null) ? null : encoding.getValue()); 172 } 173 174 /** 175 * Gets the VALUE sub type. This defines what kind of value the type has, 176 * such as "text" or "URI". 177 * <p> 178 * vCard versions: 2.1, 3.0, 4.0 179 * </p> 180 * @return the value or null if not found 181 */ 182 public ValueParameter getValue() { 183 String value = getFirst(ValueParameter.NAME); 184 if (value == null) { 185 return null; 186 } 187 ValueParameter p = ValueParameter.valueOf(value); 188 if (p == null) { 189 p = new ValueParameter(value); 190 } 191 return p; 192 } 193 194 /** 195 * Sets the VALUE sub type. This defines what kind of value the type has, 196 * such as "text" or "URI". 197 * <p> 198 * vCard versions: 2.1, 3.0, 4.0 199 * </p> 200 * @param value the value or null to remove 201 */ 202 public void setValue(ValueParameter value) { 203 replace(ValueParameter.NAME, (value == null) ? null : value.getValue()); 204 } 205 206 /** 207 * Gets the CHARSET sub type. 208 * <p> 209 * vCard versions: 2.1 210 * </p> 211 * @return the value or null if not found 212 */ 213 public String getCharset() { 214 return getFirst("CHARSET"); 215 } 216 217 /** 218 * Sets the CHARSET sub type 219 * <p> 220 * vCard versions: 2.1 221 * </p> 222 * @param charset the value or null to remove 223 */ 224 public void setCharset(String charset) { 225 replace("CHARSET", charset); 226 } 227 228 /** 229 * Gets the LANGUAGE sub type. 230 * <p> 231 * vCard versions: 2.1, 3.0, 4.0 232 * </p> 233 * @return the language (e.g. "en-US") or null if not set 234 * @see <a href="http://tools.ietf.org/html/rfc5646">RFC 5646</a> 235 */ 236 public String getLanguage() { 237 return getFirst("LANGUAGE"); 238 } 239 240 /** 241 * Sets the LANGUAGE sub type. 242 * <p> 243 * vCard versions: 2.1, 3.0, 4.0 244 * </p> 245 * @param language the language (e.g "en-US") or null to remove 246 * @see <a href="http://tools.ietf.org/html/rfc5646">RFC 5646</a> 247 */ 248 public void setLanguage(String language) { 249 replace("LANGUAGE", language); 250 } 251 252 /** 253 * Gets all TYPE sub types. 254 * <p> 255 * vCard versions: 2.1, 3.0, 4.0 256 * </p> 257 * @return the values or empty set if not found 258 */ 259 public Set<String> getTypes() { 260 return new HashSet<String>(get(TypeParameter.NAME)); 261 } 262 263 /** 264 * Adds a TYPE sub type 265 * <p> 266 * vCard versions: 2.1, 3.0, 4.0 267 * </p> 268 * @param type the value 269 */ 270 public void addType(String type) { 271 put(TypeParameter.NAME, type); 272 } 273 274 /** 275 * Gets the first TYPE sub type. 276 * <p> 277 * vCard versions: 2.1, 3.0, 4.0 278 * </p> 279 * @return the value or null if not found. 280 */ 281 public String getType() { 282 Set<String> types = getTypes(); 283 return types.isEmpty() ? null : types.iterator().next(); 284 } 285 286 /** 287 * Sets the TYPE sub type. 288 * <p> 289 * vCard versions: 2.1, 3.0, 4.0 290 * </p> 291 * @param type the value or null to remove 292 */ 293 public void setType(String type) { 294 replace(TypeParameter.NAME, type); 295 } 296 297 /** 298 * Removes a TYPE sub type. 299 * <p> 300 * vCard versions: 2.1, 3.0, 4.0 301 * </p> 302 * @param type the value to remove 303 */ 304 public void removeType(String type) { 305 remove(TypeParameter.NAME, type); 306 } 307 308 /** 309 * <p> 310 * Gets the preference value. The lower the number, the more preferred this 311 * type is compared to other types in the vCard with the same name. If a 312 * type doesn't have a preference value, then it is considered the 313 * <b>least</b> preferred. 314 * </p> 315 * 316 * <p> 317 * In the vCard below, the address on the second row is the most preferred 318 * because it has the lowest PREF value. 319 * </p> 320 * 321 * <pre> 322 * ADR;TYPE=work;PREF=2: 323 * ADR;TYPE=work;PREF=1: 324 * ADR;TYPE=home: 325 * </pre> 326 * 327 * <p> 328 * Preference values must be numeric and must be between 1 and 100. 329 * </p> 330 * 331 * <p> 332 * vCard versions: 4.0 333 * </p> 334 * @return the preference value or null if it doesn't exist or null if it 335 * couldn't be parsed into a number 336 */ 337 public Integer getPref() { 338 String pref = getFirst("PREF"); 339 if (pref == null) { 340 return null; 341 } 342 343 try { 344 return Integer.valueOf(pref); 345 } catch (NumberFormatException e) { 346 return null; 347 } 348 } 349 350 /** 351 * <p> 352 * Sets the preference value. The lower the number, the more preferred this 353 * type is compared to other types in the vCard with the same name. If a 354 * type doesn't have a preference value, then it is considered the 355 * <b>least</b> preferred. 356 * </p> 357 * 358 * <p> 359 * In the vCard below, the address on the second row is the most preferred 360 * because it has the lowest PREF value. 361 * </p> 362 * 363 * <pre> 364 * ADR;TYPE=work;PREF=2: 365 * ADR;TYPE=work;PREF=1: 366 * ADR;TYPE=home: 367 * </pre> 368 * 369 * <p> 370 * Preference values must be numeric and must be between 1 and 100. 371 * </p> 372 * 373 * <p> 374 * vCard versions: 4.0 375 * </p> 376 * @param pref the preference value or null to remove 377 * @throws IllegalArgumentException if the value is not between 1 and 100 378 */ 379 public void setPref(Integer pref) { 380 if (pref != null && (pref < 1 || pref > 100)) { 381 throw new IllegalArgumentException("Preference value must be between 1 and 100 inclusive."); 382 } 383 String value = (pref == null) ? null : pref.toString(); 384 replace("PREF", value); 385 } 386 387 /** 388 * Gets the ALTID parameter value. This is used to specify alternative 389 * representations of the same type. 390 * 391 * <p> 392 * For example, a vCard may contain multiple NOTE types that each have the 393 * same ALTID. This means that each NOTE contains a different representation 394 * of the same information. In the example below, the first three NOTEs have 395 * the same ALTID. They each contain the same message, but each is written 396 * in a different language. The other NOTEs have different (or absent) 397 * ALTIDs, which means they are not associated with the top three. 398 * </p> 399 * 400 * <pre> 401 * NOTE;ALTID=1;LANGUAGE=en: Hello world! 402 * NOTE;ALTID=1;LANGUAGE=fr: Bonjour tout le monde! 403 * NOTE;ALTID=1;LANGUAGE=es: �Hola, mundo! 404 * NOTE;ALTID=2;LANGUAGE=de: Meine Lieblingsfarbe ist blau. 405 * NOTE;ALTID=2;LANGUAGE=en: My favorite color is blue. 406 * NOTE: This vCard will self-destruct in 5 seconds. 407 * </pre> 408 * 409 * <p> 410 * vCard versions: 4.0 411 * </p> 412 * @return the ALTID or null if it doesn't exist 413 */ 414 public String getAltId() { 415 return getFirst("ALTID"); 416 } 417 418 /** 419 * Sets the ALTID parameter value. This is used to specify alternative 420 * representations of the same type. 421 * 422 * <p> 423 * For example, a vCard may contain multiple NOTE types that each have the 424 * same ALTID. This means that each NOTE contains a different representation 425 * of the same information. In the example below, the first three NOTEs have 426 * the same ALTID. They each contain the same message, but each is written 427 * in a different language. The other NOTEs have different (or absent) 428 * ALTIDs, which means they are not associated with the top three. 429 * </p> 430 * 431 * <pre> 432 * NOTE;ALTID=1;LANGUAGE=en: Hello world! 433 * NOTE;ALTID=1;LANGUAGE=fr: Bonjour tout le monde! 434 * NOTE;ALTID=1;LANGUAGE=es: �Hola, mundo! 435 * NOTE;ALTID=2;LANGUAGE=de: Meine Lieblingsfarbe ist blau. 436 * NOTE;ALTID=2;LANGUAGE=en: My favorite color is blue. 437 * NOTE: This vCard will self-destruct in 5 seconds. 438 * </pre> 439 * 440 * <p> 441 * vCard versions: 4.0 442 * </p> 443 * @param altId the ALTID or null to remove 444 */ 445 public void setAltId(String altId) { 446 replace("ALTID", altId); 447 } 448 449 /** 450 * Gets the GEO parameter value. This is used to associate global 451 * positioning information with a vCard type. It can be used with the ADR 452 * type. 453 * <p> 454 * vCard versions: 4.0 455 * </p> 456 * @return the latitude (index 0) and longitude (index 1) or null if not 457 * present or null if the parameter value was in an incorrect format 458 */ 459 public double[] getGeo() { 460 String value = getFirst("GEO"); 461 if (value == null) { 462 return null; 463 } 464 465 try { 466 GeoUri geoUri = new GeoUri(value); 467 return new double[] { geoUri.getCoordA(), geoUri.getCoordB() }; 468 } catch (IllegalArgumentException e) { 469 return null; 470 } 471 } 472 473 /** 474 * Sets the GEO parameter value. This is used to associate global 475 * positioning information with a vCard type. It can be used with the ADR 476 * type. 477 * <p> 478 * vCard versions: 4.0 479 * </p> 480 * @param latitude the latitude 481 * @param longitude the longitude 482 */ 483 public void setGeo(double latitude, double longitude) { 484 GeoUri geoUri = new GeoUri(latitude, longitude, null, null, null); 485 replace("GEO", geoUri.toString()); 486 } 487 488 /** 489 * Gets the SORT-AS parameter value(s). This contains typically two string 490 * values which the vCard should be sorted by (family and given names). This 491 * is useful if the person's last name (defined in the N property) starts 492 * with characters that should be ignored during sorting. It can be used 493 * with the N and ORG types. 494 * <p> 495 * vCard versions: 4.0 496 * </p> 497 * @return the name(s) (e.g. { "Aboville", "Christine" } if the family name 498 * is "d'Aboville" and the given name is "Christine") or empty list of the 499 * parameter doesn't exist 500 */ 501 public List<String> getSortAs() { 502 return get("SORT-AS"); 503 } 504 505 /** 506 * Sets the SORT-AS parameter value(s). This is useful with the N property 507 * when the person's last name starts with characters that should be ignored 508 * during sorting. It can be used with the N and ORG types. 509 * <p> 510 * vCard versions: 4.0 511 * </p> 512 * @param names the names in the order they should be sorted in (e.g. 513 * ["Aboville", "Christine"] if the family name is "d'Aboville" and the 514 * given name is "Christine") or empty parameter list to remove 515 */ 516 public void setSortAs(String... names) { 517 removeAll("SORT-AS"); 518 if (names != null && names.length > 0) { 519 for (String name : names) { 520 put("SORT-AS", name); 521 } 522 } 523 } 524 525 /** 526 * Gets the CALSCALE parameter value. This defines the type of calendar that 527 * is used. 528 * <p> 529 * vCard versions: 4.0 530 * </p> 531 * @return the type of calendar or null if not found 532 */ 533 public CalscaleParameter getCalscale() { 534 String value = getFirst(CalscaleParameter.NAME); 535 if (value == null) { 536 return null; 537 } 538 CalscaleParameter p = CalscaleParameter.valueOf(value); 539 if (p == null) { 540 p = new CalscaleParameter(value); 541 } 542 return p; 543 } 544 545 /** 546 * Gets the CALSCALE parameter value. This is used with date/time types and 547 * defines the type of calendar that is used. 548 * <p> 549 * vCard versions: 4.0 550 * </p> 551 * @param value the type of calendar or null to remove 552 */ 553 public void setCalscale(CalscaleParameter value) { 554 replace(CalscaleParameter.NAME, (value == null) ? null : value.getValue()); 555 } 556 557 /** 558 * <p> 559 * Gets all PID parameter values. PIDs can exist on any property where 560 * multiple instances are allowed (such as EMAIL or ADR, but not N because 561 * only 1 instance of N is allowed). 562 * </p> 563 * <p> 564 * When used in conjunction with the CLIENTPIDMAP property, it allows an 565 * individual property instance to be uniquely identifiable. This feature is 566 * made use of when two different versions of the same vCard have to be 567 * merged together (called "synchronizing"). 568 * </p> 569 * <p> 570 * vCard versions: 4.0 571 * </p> 572 * @return the PID values or empty set if there are none. Index 0 is the 573 * local ID and index 1 is the ID used to reference the CLIENTPIDMAP 574 * property. Index 0 will never be null, but index 1 may be null. 575 */ 576 public List<Integer[]> getPids() { 577 List<String> values = get("PID"); 578 List<Integer[]> pids = new ArrayList<Integer[]>(values.size()); 579 for (String value : values) { 580 try { 581 String split[] = value.split("\\."); 582 Integer pid[] = new Integer[2]; 583 pid[0] = Integer.valueOf(split[0]); 584 if (split.length > 1) { 585 pid[1] = Integer.valueOf(split[1]); 586 } 587 pids.add(pid); 588 } catch (NumberFormatException e) { 589 //skip 590 } 591 } 592 return pids; 593 } 594 595 /** 596 * <p> 597 * Adds a PID parameter value. PIDs can exist on any property where multiple 598 * instances are allowed (such as EMAIL or ADR, but not N because only 1 599 * instance of N is allowed). 600 * </p> 601 * <p> 602 * When used in conjunction with the CLIENTPIDMAP property, it allows an 603 * individual property instance to be uniquely identifiable. This feature is 604 * made use of when two different versions of the same vCard have to be 605 * merged together (called "synchronizing"). 606 * </p> 607 * <p> 608 * vCard versions: 4.0 609 * </p> 610 * @param localId the local ID 611 */ 612 public void addPid(int localId) { 613 put("PID", Integer.toString(localId)); 614 } 615 616 /** 617 * <p> 618 * Adds a PID parameter value. PIDs can exist on any property where multiple 619 * instances are allowed (such as EMAIL or ADR, but not N because only 1 620 * instance of N is allowed). 621 * </p> 622 * <p> 623 * When used in conjunction with the CLIENTPIDMAP property, it allows an 624 * individual property instance to be uniquely identifiable. This feature is 625 * made use of when two different versions of the same vCard have to be 626 * merged together (called "synchronizing"). 627 * </p> 628 * <p> 629 * vCard versions: 4.0 630 * </p> 631 * @param localId the local ID 632 * @param clientPidMapRef the ID used to reference the property's globally 633 * unique identifier in the CLIENTPIDMAP property. 634 */ 635 public void addPid(int localId, int clientPidMapRef) { 636 put("PID", localId + "." + clientPidMapRef); 637 } 638 639 /** 640 * Removes all PID values. 641 * <p> 642 * vCard versions: 4.0 643 * </p> 644 */ 645 public void removePids() { 646 removeAll("PID"); 647 } 648 649 /** 650 * Gets the MEDIATYPE parameter. This is used in properties that have a URL 651 * as a value, such as PHOTO and SOUND. It defines the content type of the 652 * referenced resource. 653 * <p> 654 * vCard versions: 4.0 655 * </p> 656 * @return the media type (e.g. "image/jpeg") or null if it doesn't exist 657 */ 658 public String getMediaType() { 659 return getFirst("MEDIATYPE"); 660 } 661 662 /** 663 * Sets the MEDIATYPE parameter. This is used in properties that have a URL 664 * as a value, such as PHOTO and SOUND. It defines the content type of the 665 * referenced resource. 666 * <p> 667 * vCard versions: 4.0 668 * </p> 669 * @param mediaType the media type (e.g. "image/jpeg") or null to remove 670 */ 671 public void setMediaType(String mediaType) { 672 replace("MEDIATYPE", mediaType); 673 } 674 675 /** 676 * Gets the LEVEL parameter. This is used to define the level of skill or 677 * level of interest the person has towards something. 678 * <p> 679 * vCard versions: 4.0 680 * </p> 681 * @return the level (e.g. "beginner") or null if not found 682 * @see <a href="http://tools.ietf.org/html/rfc6715">RFC 6715</a> 683 */ 684 public String getLevel() { 685 return getFirst(LevelParameter.NAME); 686 } 687 688 /** 689 * Sets the LEVEL parameter. This is used to define the level of skill or 690 * level of interest the person has towards something. 691 * <p> 692 * vCard versions: 4.0 693 * </p> 694 * @param level the level (e.g. "beginner") or null to remove 695 * @see <a href="http://tools.ietf.org/html/rfc6715">RFC 6715</a> 696 */ 697 public void setLevel(String level) { 698 replace(LevelParameter.NAME, level); 699 } 700 701 /** 702 * Gets the INDEX parameter. When present, it specifies the order in which a 703 * collection of multi-valued property instances should be sorted. 704 * Properties with low INDEX values are put at the beginning of the sorted 705 * list. 706 * 707 * <p> 708 * vCard versions: 4.0 709 * </p> 710 * @return the INDEX value or null if it doesn't exist or null if it 711 * couldn't be parsed into a number 712 * @see <a href="http://tools.ietf.org/html/rfc6715">RFC 6715</a> 713 */ 714 public Integer getIndex() { 715 String index = getFirst("INDEX"); 716 if (index == null) { 717 return null; 718 } 719 720 try { 721 return Integer.valueOf(index); 722 } catch (NumberFormatException e) { 723 return null; 724 } 725 } 726 727 /** 728 * Sets the INDEX parameter. When present, it specifies the order in which a 729 * collection of multi-valued property instances should be sorted. 730 * Properties with low INDEX values are put at the beginning of the sorted 731 * list. 732 * 733 * <p> 734 * vCard versions: 4.0 735 * </p> 736 * @param index the INDEX value (must be greater than 0) or null to remove 737 * @see <a href="http://tools.ietf.org/html/rfc6715">RFC 6715</a> 738 * @throws IllegalArgumentException if the value is not greater than 0 739 */ 740 public void setIndex(Integer index) { 741 if (index != null && index <= 0) { 742 throw new IllegalArgumentException("INDEX value must be greater than 0."); 743 } 744 String value = (index == null) ? null : index.toString(); 745 replace("INDEX", value); 746 } 747 }