001/** 002 * 003 * Copyright 2003-2007 Jive Software. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.jivesoftware.smackx.vcardtemp.packet; 019 020import java.io.BufferedInputStream; 021import java.io.File; 022import java.io.FileInputStream; 023import java.io.IOException; 024import java.lang.reflect.Field; 025import java.lang.reflect.Modifier; 026import java.net.URL; 027import java.security.MessageDigest; 028import java.security.NoSuchAlgorithmException; 029import java.util.HashMap; 030import java.util.Map; 031import java.util.Map.Entry; 032import java.util.logging.Level; 033import java.util.logging.Logger; 034 035import org.jivesoftware.smack.SmackException.NoResponseException; 036import org.jivesoftware.smack.SmackException.NotConnectedException; 037import org.jivesoftware.smack.XMPPConnection; 038import org.jivesoftware.smack.XMPPException.XMPPErrorException; 039import org.jivesoftware.smack.packet.IQ; 040import org.jivesoftware.smack.util.StringUtils; 041import org.jivesoftware.smack.util.stringencoder.Base64; 042 043import org.jivesoftware.smackx.vcardtemp.VCardManager; 044 045import org.jxmpp.jid.EntityBareJid; 046 047/** 048 * A VCard class for use with the 049 * <a href="http://www.jivesoftware.org/smack/" target="_blank">SMACK jabber library</a>.<p> 050 * <p/> 051 * You should refer to the 052 * <a href="http://www.xmpp.org/extensions/jep-0054.html" target="_blank">XEP-54 documentation</a>.<p> 053 * <p/> 054 * Please note that this class is incomplete but it does provide the most commonly found 055 * information in vCards. Also remember that VCard transfer is not a standard, and the protocol 056 * may change or be replaced.<p> 057 * <p/> 058 * <b>Usage:</b> 059 * <pre> 060 * <p/> 061 * // To save VCard: 062 * <p/> 063 * VCard vCard = new VCard(); 064 * vCard.setFirstName("kir"); 065 * vCard.setLastName("max"); 066 * vCard.setEmailHome("foo@fee.bar"); 067 * vCard.setJabberId("jabber@id.org"); 068 * vCard.setOrganization("Jetbrains, s.r.o"); 069 * vCard.setNickName("KIR"); 070 * <p/> 071 * vCard.setField("TITLE", "Mr"); 072 * vCard.setAddressFieldHome("STREET", "Some street"); 073 * vCard.setAddressFieldWork("CTRY", "US"); 074 * vCard.setPhoneWork("FAX", "3443233"); 075 * <p/> 076 * vCard.save(connection); 077 * <p/> 078 * // To load VCard: 079 * <p/> 080 * VCard vCard = new VCard(); 081 * vCard.load(conn); // load own VCard 082 * vCard.load(conn, "joe@foo.bar"); // load someone's VCard 083 * </pre> 084 * 085 * @author Kirill Maximov (kir@maxkir.com) 086 */ 087public class VCard extends IQ { 088 public static final String ELEMENT = "vCard"; 089 public static final String NAMESPACE = "vcard-temp"; 090 091 private static final Logger LOGGER = Logger.getLogger(VCard.class.getName()); 092 093 private static final String DEFAULT_MIME_TYPE = "image/jpeg"; 094 095 /** 096 * Phone types: 097 * VOICE?, FAX?, PAGER?, MSG?, CELL?, VIDEO?, BBS?, MODEM?, ISDN?, PCS?, PREF? 098 */ 099 private Map<String, String> homePhones = new HashMap<String, String>(); 100 private Map<String, String> workPhones = new HashMap<String, String>(); 101 102 /** 103 * Address types: 104 * POSTAL?, PARCEL?, (DOM | INTL)?, PREF?, POBOX?, EXTADR?, STREET?, LOCALITY?, 105 * REGION?, PCODE?, CTRY? 106 */ 107 private Map<String, String> homeAddr = new HashMap<String, String>(); 108 private Map<String, String> workAddr = new HashMap<String, String>(); 109 110 private String firstName; 111 private String lastName; 112 private String middleName; 113 private String prefix; 114 private String suffix; 115 116 private String emailHome; 117 private String emailWork; 118 119 private String organization; 120 private String organizationUnit; 121 122 private String photoMimeType; 123 private String photoBinval; 124 125 /** 126 * Such as DESC ROLE GEO etc.. see XEP-0054 127 */ 128 private Map<String, String> otherSimpleFields = new HashMap<String, String>(); 129 130 // fields that, as they are should not be escaped before forwarding to the server 131 private Map<String, String> otherUnescapableFields = new HashMap<String, String>(); 132 133 public VCard() { 134 super(ELEMENT, NAMESPACE); 135 } 136 137 /** 138 * Set generic VCard field. 139 * 140 * @param field value of field. Possible values: NICKNAME, PHOTO, BDAY, JABBERID, MAILER, TZ, 141 * GEO, TITLE, ROLE, LOGO, NOTE, PRODID, REV, SORT-STRING, SOUND, UID, URL, DESC. 142 */ 143 public String getField(String field) { 144 return otherSimpleFields.get(field); 145 } 146 147 /** 148 * Set generic VCard field. 149 * 150 * @param value value of field 151 * @param field field to set. See {@link #getField(String)} 152 * @see #getField(String) 153 */ 154 public void setField(String field, String value) { 155 setField(field, value, false); 156 } 157 158 /** 159 * Set generic, unescapable VCard field. If unescabale is set to true, XML maybe a part of the 160 * value. 161 * 162 * @param value value of field 163 * @param field field to set. See {@link #getField(String)} 164 * @param isUnescapable True if the value should not be escaped, and false if it should. 165 */ 166 public void setField(String field, String value, boolean isUnescapable) { 167 if (!isUnescapable) { 168 otherSimpleFields.put(field, value); 169 } 170 else { 171 otherUnescapableFields.put(field, value); 172 } 173 } 174 175 public String getFirstName() { 176 return firstName; 177 } 178 179 public void setFirstName(String firstName) { 180 this.firstName = firstName; 181 // Update FN field 182 updateFN(); 183 } 184 185 public String getLastName() { 186 return lastName; 187 } 188 189 public void setLastName(String lastName) { 190 this.lastName = lastName; 191 // Update FN field 192 updateFN(); 193 } 194 195 public String getMiddleName() { 196 return middleName; 197 } 198 199 public void setMiddleName(String middleName) { 200 this.middleName = middleName; 201 // Update FN field 202 updateFN(); 203 } 204 205 public String getPrefix() { 206 return prefix; 207 } 208 209 public void setPrefix(String prefix) { 210 this.prefix = prefix; 211 updateFN(); 212 } 213 214 public String getSuffix() { 215 return suffix; 216 } 217 218 public void setSuffix(String suffix) { 219 this.suffix = suffix; 220 updateFN(); 221 } 222 223 public String getNickName() { 224 return otherSimpleFields.get("NICKNAME"); 225 } 226 227 public void setNickName(String nickName) { 228 otherSimpleFields.put("NICKNAME", nickName); 229 } 230 231 public String getEmailHome() { 232 return emailHome; 233 } 234 235 public void setEmailHome(String email) { 236 this.emailHome = email; 237 } 238 239 public String getEmailWork() { 240 return emailWork; 241 } 242 243 public void setEmailWork(String emailWork) { 244 this.emailWork = emailWork; 245 } 246 247 public String getJabberId() { 248 return otherSimpleFields.get("JABBERID"); 249 } 250 251 public void setJabberId(String jabberId) { 252 otherSimpleFields.put("JABBERID", jabberId); 253 } 254 255 public String getOrganization() { 256 return organization; 257 } 258 259 public void setOrganization(String organization) { 260 this.organization = organization; 261 } 262 263 public String getOrganizationUnit() { 264 return organizationUnit; 265 } 266 267 public void setOrganizationUnit(String organizationUnit) { 268 this.organizationUnit = organizationUnit; 269 } 270 271 /** 272 * Get home address field. 273 * 274 * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, 275 * LOCALITY, REGION, PCODE, CTRY 276 */ 277 public String getAddressFieldHome(String addrField) { 278 return homeAddr.get(addrField); 279 } 280 281 /** 282 * Set home address field. 283 * 284 * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, 285 * LOCALITY, REGION, PCODE, CTRY 286 */ 287 public void setAddressFieldHome(String addrField, String value) { 288 homeAddr.put(addrField, value); 289 } 290 291 /** 292 * Get work address field. 293 * 294 * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, 295 * LOCALITY, REGION, PCODE, CTRY 296 */ 297 public String getAddressFieldWork(String addrField) { 298 return workAddr.get(addrField); 299 } 300 301 /** 302 * Set work address field. 303 * 304 * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, 305 * LOCALITY, REGION, PCODE, CTRY 306 */ 307 public void setAddressFieldWork(String addrField, String value) { 308 workAddr.put(addrField, value); 309 } 310 311 312 /** 313 * Set home phone number. 314 * 315 * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF 316 * @param phoneNum phone number 317 */ 318 public void setPhoneHome(String phoneType, String phoneNum) { 319 homePhones.put(phoneType, phoneNum); 320 } 321 322 /** 323 * Get home phone number. 324 * 325 * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF 326 */ 327 public String getPhoneHome(String phoneType) { 328 return homePhones.get(phoneType); 329 } 330 331 /** 332 * Set work phone number. 333 * 334 * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF 335 * @param phoneNum phone number 336 */ 337 public void setPhoneWork(String phoneType, String phoneNum) { 338 workPhones.put(phoneType, phoneNum); 339 } 340 341 /** 342 * Get work phone number. 343 * 344 * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF 345 */ 346 public String getPhoneWork(String phoneType) { 347 return workPhones.get(phoneType); 348 } 349 350 /** 351 * Set the avatar for the VCard by specifying the url to the image. 352 * 353 * @param avatarURL the url to the image(png,jpeg,gif,bmp) 354 */ 355 public void setAvatar(URL avatarURL) { 356 byte[] bytes = new byte[0]; 357 try { 358 bytes = getBytes(avatarURL); 359 } 360 catch (IOException e) { 361 LOGGER.log(Level.SEVERE, "Error getting bytes from URL: " + avatarURL, e); 362 } 363 364 setAvatar(bytes); 365 } 366 367 /** 368 * Removes the avatar from the vCard. 369 * 370 * This is done by setting the PHOTO value to the empty string as defined in XEP-0153 371 */ 372 public void removeAvatar() { 373 // Remove avatar (if any) 374 photoBinval = null; 375 photoMimeType = null; 376 } 377 378 /** 379 * Specify the bytes of the JPEG for the avatar to use. 380 * If bytes is null, then the avatar will be removed. 381 * 'image/jpeg' will be used as MIME type. 382 * 383 * @param bytes the bytes of the avatar, or null to remove the avatar data 384 */ 385 public void setAvatar(byte[] bytes) { 386 setAvatar(bytes, DEFAULT_MIME_TYPE); 387 } 388 389 /** 390 * Specify the bytes for the avatar to use as well as the mime type. 391 * 392 * @param bytes the bytes of the avatar. 393 * @param mimeType the mime type of the avatar. 394 */ 395 public void setAvatar(byte[] bytes, String mimeType) { 396 // If bytes is null, remove the avatar 397 if (bytes == null) { 398 removeAvatar(); 399 return; 400 } 401 402 // Otherwise, add to mappings. 403 String encodedImage = Base64.encodeToString(bytes); 404 405 setAvatar(encodedImage, mimeType); 406 } 407 408 /** 409 * Specify the Avatar used for this vCard. 410 * 411 * @param encodedImage the Base64 encoded image as String 412 * @param mimeType the MIME type of the image 413 */ 414 public void setAvatar(String encodedImage, String mimeType) { 415 photoBinval = encodedImage; 416 photoMimeType = mimeType; 417 } 418 419 /** 420 * Set the encoded avatar string. This is used by the provider. 421 * 422 * @param encodedAvatar the encoded avatar string. 423 * @deprecated Use {@link #setAvatar(String, String)} instead. 424 */ 425 @Deprecated 426 public void setEncodedImage(String encodedAvatar) { 427 setAvatar(encodedAvatar, DEFAULT_MIME_TYPE); 428 } 429 430 /** 431 * Return the byte representation of the avatar(if one exists), otherwise returns null if 432 * no avatar could be found. 433 * <b>Example 1</b> 434 * <pre> 435 * // Load Avatar from VCard 436 * byte[] avatarBytes = vCard.getAvatar(); 437 * <p/> 438 * // To create an ImageIcon for Swing applications 439 * ImageIcon icon = new ImageIcon(avatar); 440 * <p/> 441 * // To create just an image object from the bytes 442 * ByteArrayInputStream bais = new ByteArrayInputStream(avatar); 443 * try { 444 * Image image = ImageIO.read(bais); 445 * } 446 * catch (IOException e) { 447 * e.printStackTrace(); 448 * } 449 * </pre> 450 * 451 * @return byte representation of avatar. 452 */ 453 public byte[] getAvatar() { 454 if (photoBinval == null) { 455 return null; 456 } 457 return Base64.decode(photoBinval); 458 } 459 460 /** 461 * Returns the MIME Type of the avatar or null if none is set. 462 * 463 * @return the MIME Type of the avatar or null 464 */ 465 public String getAvatarMimeType() { 466 return photoMimeType; 467 } 468 469 /** 470 * Common code for getting the bytes of a url. 471 * 472 * @param url the url to read. 473 */ 474 public static byte[] getBytes(URL url) throws IOException { 475 final String path = url.getPath(); 476 final File file = new File(path); 477 if (file.exists()) { 478 return getFileBytes(file); 479 } 480 481 return null; 482 } 483 484 private static byte[] getFileBytes(File file) throws IOException { 485 BufferedInputStream bis = null; 486 try { 487 bis = new BufferedInputStream(new FileInputStream(file)); 488 int bytes = (int) file.length(); 489 byte[] buffer = new byte[bytes]; 490 int readBytes = bis.read(buffer); 491 if (readBytes != buffer.length) { 492 throw new IOException("Entire file not read"); 493 } 494 return buffer; 495 } 496 finally { 497 if (bis != null) { 498 bis.close(); 499 } 500 } 501 } 502 503 /** 504 * Returns the SHA-1 Hash of the Avatar image. 505 * 506 * @return the SHA-1 Hash of the Avatar image. 507 */ 508 public String getAvatarHash() { 509 byte[] bytes = getAvatar(); 510 if (bytes == null) { 511 return null; 512 } 513 514 MessageDigest digest; 515 try { 516 digest = MessageDigest.getInstance("SHA-1"); 517 } 518 catch (NoSuchAlgorithmException e) { 519 LOGGER.log(Level.SEVERE, "Failed to get message digest", e); 520 return null; 521 } 522 523 digest.update(bytes); 524 return StringUtils.encodeHex(digest.digest()); 525 } 526 527 private void updateFN() { 528 StringBuilder sb = new StringBuilder(); 529 if (firstName != null) { 530 sb.append(StringUtils.escapeForXml(firstName)).append(' '); 531 } 532 if (middleName != null) { 533 sb.append(StringUtils.escapeForXml(middleName)).append(' '); 534 } 535 if (lastName != null) { 536 sb.append(StringUtils.escapeForXml(lastName)); 537 } 538 setField("FN", sb.toString()); 539 } 540 541 /** 542 * Save this vCard for the user connected by 'connection'. XMPPConnection should be authenticated 543 * and not anonymous. 544 * 545 * @param connection the XMPPConnection to use. 546 * @throws XMPPErrorException thrown if there was an issue setting the VCard in the server. 547 * @throws NoResponseException if there was no response from the server. 548 * @throws NotConnectedException 549 * @throws InterruptedException 550 * @deprecated use {@link VCardManager#saveVCard(VCard)} instead. 551 */ 552 @Deprecated 553 public void save(XMPPConnection connection) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 554 VCardManager.getInstanceFor(connection).saveVCard(this); 555 } 556 557 /** 558 * Load VCard information for a connected user. XMPPConnection should be authenticated 559 * and not anonymous. 560 * @throws XMPPErrorException 561 * @throws NoResponseException 562 * @throws NotConnectedException 563 * @throws InterruptedException 564 * @deprecated use {@link VCardManager#loadVCard()} instead. 565 */ 566 @Deprecated 567 public void load(XMPPConnection connection) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 568 load(connection, null); 569 } 570 571 /** 572 * Load VCard information for a given user. XMPPConnection should be authenticated and not anonymous. 573 * @throws XMPPErrorException 574 * @throws NoResponseException if there was no response from the server. 575 * @throws NotConnectedException 576 * @throws InterruptedException 577 * @deprecated use {@link VCardManager#loadVCard(EntityBareJid)} instead. 578 */ 579 @Deprecated 580 public void load(XMPPConnection connection, EntityBareJid user) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 581 VCard result = VCardManager.getInstanceFor(connection).loadVCard(user); 582 copyFieldsFrom(result); 583 } 584 585 @Override 586 protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { 587 if (!hasContent()) { 588 xml.setEmptyElement(); 589 return xml; 590 } 591 xml.rightAngleBracket(); 592 if (hasNameField()) { 593 xml.openElement("N"); 594 xml.optElement("FAMILY", lastName); 595 xml.optElement("GIVEN", firstName); 596 xml.optElement("MIDDLE", middleName); 597 xml.optElement("PREFIX", prefix); 598 xml.optElement("SUFFIX", suffix); 599 xml.closeElement("N"); 600 } 601 if (hasOrganizationFields()) { 602 xml.openElement("ORG"); 603 xml.optElement("ORGNAME", organization); 604 xml.optElement("ORGUNIT", organizationUnit); 605 xml.closeElement("ORG"); 606 } 607 for (Entry<String, String> entry : otherSimpleFields.entrySet()) { 608 xml.optElement(entry.getKey(), entry.getValue()); 609 } 610 for (Entry<String, String> entry : otherUnescapableFields.entrySet()) { 611 final String value = entry.getValue(); 612 if (value == null) { 613 continue; 614 } 615 xml.openElement(entry.getKey()); 616 xml.append(value); 617 xml.closeElement(entry.getKey()); 618 } 619 if (photoBinval != null) { 620 xml.openElement("PHOTO"); 621 xml.escapedElement("BINVAL", photoBinval); 622 xml.element("TYPE", photoMimeType); 623 xml.closeElement("PHOTO"); 624 } 625 if (emailWork != null) { 626 xml.openElement("EMAIL"); 627 xml.emptyElement("WORK"); 628 xml.emptyElement("INTERNET"); 629 xml.emptyElement("PREF"); 630 xml.element("USERID", emailWork); 631 xml.closeElement("EMAIL"); 632 } 633 if (emailHome != null) { 634 xml.openElement("EMAIL"); 635 xml.emptyElement("HOME"); 636 xml.emptyElement("INTERNET"); 637 xml.emptyElement("PREF"); 638 xml.element("USERID", emailHome); 639 xml.closeElement("EMAIL"); 640 } 641 for (Entry<String, String> phone : workPhones.entrySet()) { 642 final String number = phone.getValue(); 643 if (number == null) { 644 continue; 645 } 646 xml.openElement("TEL"); 647 xml.emptyElement("WORK"); 648 xml.emptyElement(phone.getKey()); 649 xml.element("NUMBER", number); 650 xml.closeElement("TEL"); 651 } 652 for (Entry<String, String> phone : homePhones.entrySet()) { 653 final String number = phone.getValue(); 654 if (number == null) { 655 continue; 656 } 657 xml.openElement("TEL"); 658 xml.emptyElement("HOME"); 659 xml.emptyElement(phone.getKey()); 660 xml.element("NUMBER", number); 661 xml.closeElement("TEL"); 662 } 663 if (!workAddr.isEmpty()) { 664 xml.openElement("ADR"); 665 xml.emptyElement("WORK"); 666 for (Entry<String, String> entry : workAddr.entrySet()) { 667 final String value = entry.getValue(); 668 if (value == null) { 669 continue; 670 } 671 xml.element(entry.getKey(), value); 672 } 673 xml.closeElement("ADR"); 674 } 675 if (!homeAddr.isEmpty()) { 676 xml.openElement("ADR"); 677 xml.emptyElement("HOME"); 678 for (Entry<String, String> entry : homeAddr.entrySet()) { 679 final String value = entry.getValue(); 680 if (value == null) { 681 continue; 682 } 683 xml.element(entry.getKey(), value); 684 } 685 xml.closeElement("ADR"); 686 } 687 return xml; 688 } 689 690 private void copyFieldsFrom(VCard from) { 691 Field[] fields = VCard.class.getDeclaredFields(); 692 for (Field field : fields) { 693 if (field.getDeclaringClass() == VCard.class && 694 !Modifier.isFinal(field.getModifiers())) { 695 try { 696 field.setAccessible(true); 697 field.set(this, field.get(from)); 698 } 699 catch (IllegalAccessException e) { 700 throw new RuntimeException("This cannot happen:" + field, e); 701 } 702 } 703 } 704 } 705 706 private boolean hasContent() { 707 // noinspection OverlyComplexBooleanExpression 708 return hasNameField() 709 || hasOrganizationFields() 710 || emailHome != null 711 || emailWork != null 712 || otherSimpleFields.size() > 0 713 || otherUnescapableFields.size() > 0 714 || homeAddr.size() > 0 715 || homePhones.size() > 0 716 || workAddr.size() > 0 717 || workPhones.size() > 0 718 || photoBinval != null 719 ; 720 } 721 722 private boolean hasNameField() { 723 return firstName != null || lastName != null || middleName != null 724 || prefix != null || suffix != null; 725 } 726 727 private boolean hasOrganizationFields() { 728 return organization != null || organizationUnit != null; 729 } 730 731 // Used in tests: 732 733 @Override 734 public boolean equals(Object o) { 735 if (this == o) return true; 736 if (o == null || getClass() != o.getClass()) return false; 737 738 final VCard vCard = (VCard) o; 739 740 if (emailHome != null ? !emailHome.equals(vCard.emailHome) : vCard.emailHome != null) { 741 return false; 742 } 743 if (emailWork != null ? !emailWork.equals(vCard.emailWork) : vCard.emailWork != null) { 744 return false; 745 } 746 if (firstName != null ? !firstName.equals(vCard.firstName) : vCard.firstName != null) { 747 return false; 748 } 749 if (!homeAddr.equals(vCard.homeAddr)) { 750 return false; 751 } 752 if (!homePhones.equals(vCard.homePhones)) { 753 return false; 754 } 755 if (lastName != null ? !lastName.equals(vCard.lastName) : vCard.lastName != null) { 756 return false; 757 } 758 if (middleName != null ? !middleName.equals(vCard.middleName) : vCard.middleName != null) { 759 return false; 760 } 761 if (organization != null ? 762 !organization.equals(vCard.organization) : vCard.organization != null) { 763 return false; 764 } 765 if (organizationUnit != null ? 766 !organizationUnit.equals(vCard.organizationUnit) : vCard.organizationUnit != null) { 767 return false; 768 } 769 if (!otherSimpleFields.equals(vCard.otherSimpleFields)) { 770 return false; 771 } 772 if (!workAddr.equals(vCard.workAddr)) { 773 return false; 774 } 775 if (photoBinval != null ? !photoBinval.equals(vCard.photoBinval) : vCard.photoBinval != null) { 776 return false; 777 } 778 779 return workPhones.equals(vCard.workPhones); 780 } 781 782 @Override 783 public int hashCode() { 784 int result; 785 result = homePhones.hashCode(); 786 result = 29 * result + workPhones.hashCode(); 787 result = 29 * result + homeAddr.hashCode(); 788 result = 29 * result + workAddr.hashCode(); 789 result = 29 * result + (firstName != null ? firstName.hashCode() : 0); 790 result = 29 * result + (lastName != null ? lastName.hashCode() : 0); 791 result = 29 * result + (middleName != null ? middleName.hashCode() : 0); 792 result = 29 * result + (emailHome != null ? emailHome.hashCode() : 0); 793 result = 29 * result + (emailWork != null ? emailWork.hashCode() : 0); 794 result = 29 * result + (organization != null ? organization.hashCode() : 0); 795 result = 29 * result + (organizationUnit != null ? organizationUnit.hashCode() : 0); 796 result = 29 * result + otherSimpleFields.hashCode(); 797 result = 29 * result + (photoBinval != null ? photoBinval.hashCode() : 0); 798 return result; 799 } 800 801} 802