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