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 * 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 * 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 * 058 * <b>Usage:</b> 059 * <pre> 060 * 061 * // To save VCard: 062 * 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 * 071 * vCard.setField("TITLE", "Mr"); 072 * vCard.setAddressFieldHome("STREET", "Some street"); 073 * vCard.setAddressFieldWork("CTRY", "US"); 074 * vCard.setPhoneWork("FAX", "3443233"); 075 * 076 * vCard.save(connection); 077 * 078 * // To load VCard: 079 * 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 final 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 final Map<String, String> homePhones = new HashMap<>(); 100 private final Map<String, String> workPhones = new HashMap<>(); 101 102 /** 103 * Address types: 104 * POSTAL?, PARCEL?, (DOM | INTL)?, PREF?, POBOX?, EXTADR?, STREET?, LOCALITY?, 105 * REGION?, PCODE?, CTRY? 106 */ 107 private final Map<String, String> homeAddr = new HashMap<>(); 108 private final Map<String, String> workAddr = new HashMap<>(); 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 final Map<String, String> otherSimpleFields = new HashMap<>(); 129 130 // fields that, as they are should not be escaped before forwarding to the server 131 private final Map<String, String> otherUnescapableFields = new HashMap<>(); 132 133 public VCard() { 134 super(ELEMENT, NAMESPACE); 135 } 136 137 /** 138 * Get the content of a 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 * @return content of field. 143 */ 144 public String getField(String field) { 145 return otherSimpleFields.get(field); 146 } 147 148 /** 149 * Set generic VCard field. 150 * 151 * @param value value of field 152 * @param field field to set. See {@link #getField(String)} 153 * @see #getField(String) 154 */ 155 public void setField(String field, String value) { 156 setField(field, value, false); 157 } 158 159 /** 160 * Set generic, unescapable VCard field. If unescapable is set to true, XML maybe a part of the 161 * value. 162 * 163 * @param value value of field 164 * @param field field to set. See {@link #getField(String)} 165 * @param isUnescapable True if the value should not be escaped, and false if it should. 166 */ 167 public void setField(String field, String value, boolean isUnescapable) { 168 if (!isUnescapable) { 169 otherSimpleFields.put(field, value); 170 } 171 else { 172 otherUnescapableFields.put(field, value); 173 } 174 } 175 176 public String getFirstName() { 177 return firstName; 178 } 179 180 public void setFirstName(String firstName) { 181 this.firstName = firstName; 182 // Update FN field 183 updateFN(); 184 } 185 186 public String getLastName() { 187 return lastName; 188 } 189 190 public void setLastName(String lastName) { 191 this.lastName = lastName; 192 // Update FN field 193 updateFN(); 194 } 195 196 public String getMiddleName() { 197 return middleName; 198 } 199 200 public void setMiddleName(String middleName) { 201 this.middleName = middleName; 202 // Update FN field 203 updateFN(); 204 } 205 206 public String getPrefix() { 207 return prefix; 208 } 209 210 public void setPrefix(String prefix) { 211 this.prefix = prefix; 212 updateFN(); 213 } 214 215 public String getSuffix() { 216 return suffix; 217 } 218 219 public void setSuffix(String suffix) { 220 this.suffix = suffix; 221 updateFN(); 222 } 223 224 public String getNickName() { 225 return otherSimpleFields.get("NICKNAME"); 226 } 227 228 public void setNickName(String nickName) { 229 otherSimpleFields.put("NICKNAME", nickName); 230 } 231 232 public String getEmailHome() { 233 return emailHome; 234 } 235 236 public void setEmailHome(String email) { 237 this.emailHome = email; 238 } 239 240 public String getEmailWork() { 241 return emailWork; 242 } 243 244 public void setEmailWork(String emailWork) { 245 this.emailWork = emailWork; 246 } 247 248 public String getJabberId() { 249 return otherSimpleFields.get("JABBERID"); 250 } 251 252 public void setJabberId(CharSequence jabberId) { 253 otherSimpleFields.put("JABBERID", jabberId.toString()); 254 } 255 256 public String getOrganization() { 257 return organization; 258 } 259 260 public void setOrganization(String organization) { 261 this.organization = organization; 262 } 263 264 public String getOrganizationUnit() { 265 return organizationUnit; 266 } 267 268 public void setOrganizationUnit(String organizationUnit) { 269 this.organizationUnit = organizationUnit; 270 } 271 272 /** 273 * Get home address field. 274 * 275 * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, 276 * LOCALITY, REGION, PCODE, CTRY 277 * @return content of home address field. 278 */ 279 public String getAddressFieldHome(String addrField) { 280 return homeAddr.get(addrField); 281 } 282 283 /** 284 * Set home address field. 285 * 286 * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, 287 * LOCALITY, REGION, PCODE, CTRY 288 * @param value new value for the field. 289 */ 290 public void setAddressFieldHome(String addrField, String value) { 291 homeAddr.put(addrField, value); 292 } 293 294 /** 295 * Get work address field. 296 * 297 * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, 298 * LOCALITY, REGION, PCODE, CTRY 299 * @return content of work address field. 300 */ 301 public String getAddressFieldWork(String addrField) { 302 return workAddr.get(addrField); 303 } 304 305 /** 306 * Set work address field. 307 * 308 * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, 309 * LOCALITY, REGION, PCODE, CTRY 310 * @param value new value for the field. 311 */ 312 public void setAddressFieldWork(String addrField, String value) { 313 workAddr.put(addrField, value); 314 } 315 316 317 /** 318 * Set home phone number. 319 * 320 * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF 321 * @param phoneNum phone number 322 */ 323 public void setPhoneHome(String phoneType, String phoneNum) { 324 homePhones.put(phoneType, phoneNum); 325 } 326 327 /** 328 * Get home phone number. 329 * 330 * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF 331 * @return content of home phone number. 332 */ 333 public String getPhoneHome(String phoneType) { 334 return homePhones.get(phoneType); 335 } 336 337 /** 338 * Set work phone number. 339 * 340 * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF 341 * @param phoneNum phone number 342 */ 343 public void setPhoneWork(String phoneType, String phoneNum) { 344 workPhones.put(phoneType, phoneNum); 345 } 346 347 /** 348 * Get work phone number. 349 * 350 * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF 351 * @return content of work phone number. 352 */ 353 public String getPhoneWork(String phoneType) { 354 return workPhones.get(phoneType); 355 } 356 357 /** 358 * Set the avatar for the VCard by specifying the url to the image. 359 * 360 * @param avatarURL the url to the image(png, jpeg, gif, bmp) 361 */ 362 public void setAvatar(URL avatarURL) { 363 byte[] bytes = new byte[0]; 364 try { 365 bytes = getBytes(avatarURL); 366 } 367 catch (IOException e) { 368 LOGGER.log(Level.SEVERE, "Error getting bytes from URL: " + avatarURL, e); 369 } 370 371 setAvatar(bytes); 372 } 373 374 /** 375 * Removes the avatar from the vCard. 376 * 377 * This is done by setting the PHOTO value to the empty string as defined in XEP-0153 378 */ 379 public void removeAvatar() { 380 // Remove avatar (if any) 381 photoBinval = null; 382 photoMimeType = null; 383 } 384 385 /** 386 * Specify the bytes of the JPEG for the avatar to use. 387 * If bytes is null, then the avatar will be removed. 388 * 'image/jpeg' will be used as MIME type. 389 * 390 * @param bytes the bytes of the avatar, or null to remove the avatar data 391 */ 392 public void setAvatar(byte[] bytes) { 393 setAvatar(bytes, DEFAULT_MIME_TYPE); 394 } 395 396 /** 397 * Specify the bytes for the avatar to use as well as the mime type. 398 * 399 * @param bytes the bytes of the avatar. 400 * @param mimeType the mime type of the avatar. 401 */ 402 public void setAvatar(byte[] bytes, String mimeType) { 403 // If bytes is null, remove the avatar 404 if (bytes == null) { 405 removeAvatar(); 406 return; 407 } 408 409 // Otherwise, add to mappings. 410 String encodedImage = Base64.encodeToString(bytes); 411 412 setAvatar(encodedImage, mimeType); 413 } 414 415 /** 416 * Specify the Avatar used for this vCard. 417 * 418 * @param encodedImage the Base64 encoded image as String 419 * @param mimeType the MIME type of the image 420 */ 421 public void setAvatar(String encodedImage, String mimeType) { 422 photoBinval = encodedImage; 423 photoMimeType = mimeType; 424 } 425 426 /** 427 * Set the encoded avatar string. This is used by the provider. 428 * 429 * @param encodedAvatar the encoded avatar string. 430 * @deprecated Use {@link #setAvatar(String, String)} instead. 431 */ 432 @Deprecated 433 public void setEncodedImage(String encodedAvatar) { 434 setAvatar(encodedAvatar, DEFAULT_MIME_TYPE); 435 } 436 437 /** 438 * Return the byte representation of the avatar(if one exists), otherwise returns null if 439 * no avatar could be found. 440 * <b>Example 1</b> 441 * <pre> 442 * // Load Avatar from VCard 443 * byte[] avatarBytes = vCard.getAvatar(); 444 * 445 * // To create an ImageIcon for Swing applications 446 * ImageIcon icon = new ImageIcon(avatar); 447 * 448 * // To create just an image object from the bytes 449 * ByteArrayInputStream bais = new ByteArrayInputStream(avatar); 450 * try { 451 * Image image = ImageIO.read(bais); 452 * } 453 * catch (IOException e) { 454 * e.printStackTrace(); 455 * } 456 * </pre> 457 * 458 * @return byte representation of avatar. 459 */ 460 public byte[] getAvatar() { 461 if (photoBinval == null) { 462 return null; 463 } 464 return Base64.decode(photoBinval); 465 } 466 467 /** 468 * Returns the MIME Type of the avatar or null if none is set. 469 * 470 * @return the MIME Type of the avatar or null 471 */ 472 public String getAvatarMimeType() { 473 return photoMimeType; 474 } 475 476 /** 477 * Common code for getting the bytes of a url. 478 * 479 * @param url the url to read. 480 * @return bytes of the file pointed to by URL. 481 * @throws IOException if an IOException occurs while reading the file. 482 */ 483 public static byte[] getBytes(URL url) throws IOException { 484 final String path = url.getPath(); 485 final File file = new File(path); 486 if (file.exists()) { 487 return getFileBytes(file); 488 } 489 490 return null; 491 } 492 493 private static byte[] getFileBytes(File file) throws IOException { 494 try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))) { 495 int bytes = (int) file.length(); 496 byte[] buffer = new byte[bytes]; 497 int readBytes = bis.read(buffer); 498 if (readBytes != buffer.length) { 499 throw new IOException("Entire file not read"); 500 } 501 return buffer; 502 } 503 } 504 505 /** 506 * Returns the SHA-1 Hash of the Avatar image. 507 * 508 * @return the SHA-1 Hash of the Avatar image. 509 */ 510 public String getAvatarHash() { 511 byte[] bytes = getAvatar(); 512 if (bytes == null) { 513 return null; 514 } 515 516 MessageDigest digest; 517 try { 518 digest = MessageDigest.getInstance("SHA-1"); 519 } 520 catch (NoSuchAlgorithmException e) { 521 LOGGER.log(Level.SEVERE, "Failed to get message digest", e); 522 return null; 523 } 524 525 digest.update(bytes); 526 return StringUtils.encodeHex(digest.digest()); 527 } 528 529 private void updateFN() { 530 StringBuilder sb = new StringBuilder(); 531 if (firstName != null) { 532 sb.append(StringUtils.escapeForXml(firstName)).append(' '); 533 } 534 if (middleName != null) { 535 sb.append(StringUtils.escapeForXml(middleName)).append(' '); 536 } 537 if (lastName != null) { 538 sb.append(StringUtils.escapeForXml(lastName)); 539 } 540 setField("FN", sb.toString()); 541 } 542 543 /** 544 * Save this vCard for the user connected by 'connection'. XMPPConnection should be authenticated 545 * and not anonymous. 546 * 547 * @param connection the XMPPConnection to use. 548 * @throws XMPPErrorException thrown if there was an issue setting the VCard in the server. 549 * @throws NoResponseException if there was no response from the server. 550 * @throws NotConnectedException if the XMPP connection is not connected. 551 * @throws InterruptedException if the calling thread was interrupted. 552 * @deprecated use {@link VCardManager#saveVCard(VCard)} instead. 553 */ 554 @Deprecated 555 public void save(XMPPConnection connection) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 556 VCardManager.getInstanceFor(connection).saveVCard(this); 557 } 558 559 /** 560 * Load VCard information for a connected user. XMPPConnection should be authenticated 561 * and not anonymous. 562 * 563 * @param connection connection. 564 * @throws XMPPErrorException if there was an XMPP error returned. 565 * @throws NoResponseException if there was no response from the remote entity. 566 * @throws NotConnectedException if the XMPP connection is not connected. 567 * @throws InterruptedException if the calling thread was interrupted. 568 * @deprecated use {@link VCardManager#loadVCard()} instead. 569 */ 570 @Deprecated 571 public void load(XMPPConnection connection) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 572 load(connection, null); 573 } 574 575 /** 576 * Load VCard information for a given user. XMPPConnection should be authenticated and not anonymous. 577 * 578 * @param connection connection. 579 * @param user user whos information we want to load. 580 * 581 * @throws XMPPErrorException if there was an XMPP error returned. 582 * @throws NoResponseException if there was no response from the server. 583 * @throws NotConnectedException if the XMPP connection is not connected. 584 * @throws InterruptedException if the calling thread was interrupted. 585 * @deprecated use {@link VCardManager#loadVCard(EntityBareJid)} instead. 586 */ 587 @Deprecated 588 public void load(XMPPConnection connection, EntityBareJid user) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 589 VCard result = VCardManager.getInstanceFor(connection).loadVCard(user); 590 copyFieldsFrom(result); 591 } 592 593 @Override 594 protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { 595 if (!hasContent()) { 596 xml.setEmptyElement(); 597 return xml; 598 } 599 xml.rightAngleBracket(); 600 if (hasNameField()) { 601 xml.openElement("N"); 602 xml.optElement("FAMILY", lastName); 603 xml.optElement("GIVEN", firstName); 604 xml.optElement("MIDDLE", middleName); 605 xml.optElement("PREFIX", prefix); 606 xml.optElement("SUFFIX", suffix); 607 xml.closeElement("N"); 608 } 609 if (hasOrganizationFields()) { 610 xml.openElement("ORG"); 611 xml.optElement("ORGNAME", organization); 612 xml.optElement("ORGUNIT", organizationUnit); 613 xml.closeElement("ORG"); 614 } 615 for (Entry<String, String> entry : otherSimpleFields.entrySet()) { 616 xml.optElement(entry.getKey(), entry.getValue()); 617 } 618 for (Entry<String, String> entry : otherUnescapableFields.entrySet()) { 619 final String value = entry.getValue(); 620 if (value == null) { 621 continue; 622 } 623 xml.openElement(entry.getKey()); 624 xml.append(value); 625 xml.closeElement(entry.getKey()); 626 } 627 if (photoBinval != null) { 628 xml.openElement("PHOTO"); 629 xml.escapedElement("BINVAL", photoBinval); 630 xml.element("TYPE", photoMimeType); 631 xml.closeElement("PHOTO"); 632 } 633 if (emailWork != null) { 634 xml.openElement("EMAIL"); 635 xml.emptyElement("WORK"); 636 xml.emptyElement("INTERNET"); 637 xml.emptyElement("PREF"); 638 xml.element("USERID", emailWork); 639 xml.closeElement("EMAIL"); 640 } 641 if (emailHome != null) { 642 xml.openElement("EMAIL"); 643 xml.emptyElement("HOME"); 644 xml.emptyElement("INTERNET"); 645 xml.emptyElement("PREF"); 646 xml.element("USERID", emailHome); 647 xml.closeElement("EMAIL"); 648 } 649 for (Entry<String, String> phone : workPhones.entrySet()) { 650 final String number = phone.getValue(); 651 if (number == null) { 652 continue; 653 } 654 xml.openElement("TEL"); 655 xml.emptyElement("WORK"); 656 xml.emptyElement(phone.getKey()); 657 xml.element("NUMBER", number); 658 xml.closeElement("TEL"); 659 } 660 for (Entry<String, String> phone : homePhones.entrySet()) { 661 final String number = phone.getValue(); 662 if (number == null) { 663 continue; 664 } 665 xml.openElement("TEL"); 666 xml.emptyElement("HOME"); 667 xml.emptyElement(phone.getKey()); 668 xml.element("NUMBER", number); 669 xml.closeElement("TEL"); 670 } 671 if (!workAddr.isEmpty()) { 672 xml.openElement("ADR"); 673 xml.emptyElement("WORK"); 674 for (Entry<String, String> entry : workAddr.entrySet()) { 675 final String value = entry.getValue(); 676 if (value == null) { 677 continue; 678 } 679 xml.element(entry.getKey(), value); 680 } 681 xml.closeElement("ADR"); 682 } 683 if (!homeAddr.isEmpty()) { 684 xml.openElement("ADR"); 685 xml.emptyElement("HOME"); 686 for (Entry<String, String> entry : homeAddr.entrySet()) { 687 final String value = entry.getValue(); 688 if (value == null) { 689 continue; 690 } 691 xml.element(entry.getKey(), value); 692 } 693 xml.closeElement("ADR"); 694 } 695 return xml; 696 } 697 698 private void copyFieldsFrom(VCard from) { 699 Field[] fields = VCard.class.getDeclaredFields(); 700 for (Field field : fields) { 701 if (field.getDeclaringClass() == VCard.class && 702 !Modifier.isFinal(field.getModifiers())) { 703 try { 704 field.setAccessible(true); 705 field.set(this, field.get(from)); 706 } 707 catch (IllegalAccessException e) { 708 throw new RuntimeException("This cannot happen:" + field, e); 709 } 710 } 711 } 712 } 713 714 private boolean hasContent() { 715 // noinspection OverlyComplexBooleanExpression 716 return hasNameField() 717 || hasOrganizationFields() 718 || emailHome != null 719 || emailWork != null 720 || otherSimpleFields.size() > 0 721 || otherUnescapableFields.size() > 0 722 || homeAddr.size() > 0 723 || homePhones.size() > 0 724 || workAddr.size() > 0 725 || workPhones.size() > 0 726 || photoBinval != null 727 ; 728 } 729 730 private boolean hasNameField() { 731 return firstName != null || lastName != null || middleName != null 732 || prefix != null || suffix != null; 733 } 734 735 private boolean hasOrganizationFields() { 736 return organization != null || organizationUnit != null; 737 } 738 739 // Used in tests: 740 741 @Override 742 public boolean equals(Object o) { 743 if (this == o) return true; 744 if (o == null || getClass() != o.getClass()) return false; 745 746 final VCard vCard = (VCard) o; 747 748 if (emailHome != null ? !emailHome.equals(vCard.emailHome) : vCard.emailHome != null) { 749 return false; 750 } 751 if (emailWork != null ? !emailWork.equals(vCard.emailWork) : vCard.emailWork != null) { 752 return false; 753 } 754 if (firstName != null ? !firstName.equals(vCard.firstName) : vCard.firstName != null) { 755 return false; 756 } 757 if (!homeAddr.equals(vCard.homeAddr)) { 758 return false; 759 } 760 if (!homePhones.equals(vCard.homePhones)) { 761 return false; 762 } 763 if (lastName != null ? !lastName.equals(vCard.lastName) : vCard.lastName != null) { 764 return false; 765 } 766 if (middleName != null ? !middleName.equals(vCard.middleName) : vCard.middleName != null) { 767 return false; 768 } 769 if (organization != null ? 770 !organization.equals(vCard.organization) : vCard.organization != null) { 771 return false; 772 } 773 if (organizationUnit != null ? 774 !organizationUnit.equals(vCard.organizationUnit) : vCard.organizationUnit != null) { 775 return false; 776 } 777 if (!otherSimpleFields.equals(vCard.otherSimpleFields)) { 778 return false; 779 } 780 if (!workAddr.equals(vCard.workAddr)) { 781 return false; 782 } 783 if (photoBinval != null ? !photoBinval.equals(vCard.photoBinval) : vCard.photoBinval != null) { 784 return false; 785 } 786 787 return workPhones.equals(vCard.workPhones); 788 } 789 790 @Override 791 public int hashCode() { 792 int result; 793 result = homePhones.hashCode(); 794 result = 29 * result + workPhones.hashCode(); 795 result = 29 * result + homeAddr.hashCode(); 796 result = 29 * result + workAddr.hashCode(); 797 result = 29 * result + (firstName != null ? firstName.hashCode() : 0); 798 result = 29 * result + (lastName != null ? lastName.hashCode() : 0); 799 result = 29 * result + (middleName != null ? middleName.hashCode() : 0); 800 result = 29 * result + (emailHome != null ? emailHome.hashCode() : 0); 801 result = 29 * result + (emailWork != null ? emailWork.hashCode() : 0); 802 result = 29 * result + (organization != null ? organization.hashCode() : 0); 803 result = 29 * result + (organizationUnit != null ? organizationUnit.hashCode() : 0); 804 result = 29 * result + otherSimpleFields.hashCode(); 805 result = 29 * result + (photoBinval != null ? photoBinval.hashCode() : 0); 806 return result; 807 } 808 809} 810