VCard.java

  1. /**
  2.  *
  3.  * Copyright 2003-2007 Jive Software.
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.jivesoftware.smackx.vcardtemp.packet;

  18. import java.io.BufferedInputStream;
  19. import java.io.File;
  20. import java.io.FileInputStream;
  21. import java.io.IOException;
  22. import java.lang.reflect.Field;
  23. import java.lang.reflect.Modifier;
  24. import java.net.URL;
  25. import java.security.MessageDigest;
  26. import java.security.NoSuchAlgorithmException;
  27. import java.util.HashMap;
  28. import java.util.Map;
  29. import java.util.Map.Entry;
  30. import java.util.logging.Level;
  31. import java.util.logging.Logger;

  32. import org.jivesoftware.smack.SmackException.NoResponseException;
  33. import org.jivesoftware.smack.SmackException.NotConnectedException;
  34. import org.jivesoftware.smack.XMPPConnection;
  35. import org.jivesoftware.smack.XMPPException.XMPPErrorException;
  36. import org.jivesoftware.smack.packet.IQ;
  37. import org.jivesoftware.smack.util.StringUtils;
  38. import org.jivesoftware.smack.util.stringencoder.Base64;
  39. import org.jivesoftware.smackx.vcardtemp.VCardManager;
  40. import org.jxmpp.jid.BareJid;

  41. /**
  42.  * A VCard class for use with the
  43.  * <a href="http://www.jivesoftware.org/smack/" target="_blank">SMACK jabber library</a>.<p>
  44.  * <p/>
  45.  * You should refer to the
  46.  * <a href="http://www.xmpp.org/extensions/jep-0054.html" target="_blank">XEP-54 documentation</a>.<p>
  47.  * <p/>
  48.  * Please note that this class is incomplete but it does provide the most commonly found
  49.  * information in vCards. Also remember that VCard transfer is not a standard, and the protocol
  50.  * may change or be replaced.<p>
  51.  * <p/>
  52.  * <b>Usage:</b>
  53.  * <pre>
  54.  * <p/>
  55.  * // To save VCard:
  56.  * <p/>
  57.  * VCard vCard = new VCard();
  58.  * vCard.setFirstName("kir");
  59.  * vCard.setLastName("max");
  60.  * vCard.setEmailHome("foo@fee.bar");
  61.  * vCard.setJabberId("jabber@id.org");
  62.  * vCard.setOrganization("Jetbrains, s.r.o");
  63.  * vCard.setNickName("KIR");
  64.  * <p/>
  65.  * vCard.setField("TITLE", "Mr");
  66.  * vCard.setAddressFieldHome("STREET", "Some street");
  67.  * vCard.setAddressFieldWork("CTRY", "US");
  68.  * vCard.setPhoneWork("FAX", "3443233");
  69.  * <p/>
  70.  * vCard.save(connection);
  71.  * <p/>
  72.  * // To load VCard:
  73.  * <p/>
  74.  * VCard vCard = new VCard();
  75.  * vCard.load(conn); // load own VCard
  76.  * vCard.load(conn, "joe@foo.bar"); // load someone's VCard
  77.  * </pre>
  78.  *
  79.  * @author Kirill Maximov (kir@maxkir.com)
  80.  */
  81. public class VCard extends IQ {
  82.     public static final String ELEMENT = "vCard";
  83.     public static final String NAMESPACE = "vcard-temp";

  84.     private static final Logger LOGGER = Logger.getLogger(VCard.class.getName());
  85.    
  86.     private static final String DEFAULT_MIME_TYPE = "image/jpeg";
  87.    
  88.     /**
  89.      * Phone types:
  90.      * VOICE?, FAX?, PAGER?, MSG?, CELL?, VIDEO?, BBS?, MODEM?, ISDN?, PCS?, PREF?
  91.      */
  92.     private Map<String, String> homePhones = new HashMap<String, String>();
  93.     private Map<String, String> workPhones = new HashMap<String, String>();

  94.     /**
  95.      * Address types:
  96.      * POSTAL?, PARCEL?, (DOM | INTL)?, PREF?, POBOX?, EXTADR?, STREET?, LOCALITY?,
  97.      * REGION?, PCODE?, CTRY?
  98.      */
  99.     private Map<String, String> homeAddr = new HashMap<String, String>();
  100.     private Map<String, String> workAddr = new HashMap<String, String>();

  101.     private String firstName;
  102.     private String lastName;
  103.     private String middleName;

  104.     private String emailHome;
  105.     private String emailWork;

  106.     private String organization;
  107.     private String organizationUnit;

  108.     private String photoMimeType;
  109.     private String photoBinval;

  110.     /**
  111.      * Such as DESC ROLE GEO etc.. see XEP-0054
  112.      */
  113.     private Map<String, String> otherSimpleFields = new HashMap<String, String>();

  114.     // fields that, as they are should not be escaped before forwarding to the server
  115.     private Map<String, String> otherUnescapableFields = new HashMap<String, String>();

  116.     public VCard() {
  117.         super(ELEMENT, NAMESPACE);
  118.     }

  119.     /**
  120.      * Set generic VCard field.
  121.      *
  122.      * @param field value of field. Possible values: NICKNAME, PHOTO, BDAY, JABBERID, MAILER, TZ,
  123.      *              GEO, TITLE, ROLE, LOGO, NOTE, PRODID, REV, SORT-STRING, SOUND, UID, URL, DESC.
  124.      */
  125.     public String getField(String field) {
  126.         return otherSimpleFields.get(field);
  127.     }

  128.     /**
  129.      * Set generic VCard field.
  130.      *
  131.      * @param value value of field
  132.      * @param field field to set. See {@link #getField(String)}
  133.      * @see #getField(String)
  134.      */
  135.     public void setField(String field, String value) {
  136.         setField(field, value, false);
  137.     }

  138.     /**
  139.      * Set generic, unescapable VCard field. If unescabale is set to true, XML maybe a part of the
  140.      * value.
  141.      *
  142.      * @param value         value of field
  143.      * @param field         field to set. See {@link #getField(String)}
  144.      * @param isUnescapable True if the value should not be escaped, and false if it should.
  145.      */
  146.     public void setField(String field, String value, boolean isUnescapable) {
  147.         if (!isUnescapable) {
  148.             otherSimpleFields.put(field, value);
  149.         }
  150.         else {
  151.             otherUnescapableFields.put(field, value);
  152.         }
  153.     }

  154.     public String getFirstName() {
  155.         return firstName;
  156.     }

  157.     public void setFirstName(String firstName) {
  158.         this.firstName = firstName;
  159.         // Update FN field
  160.         updateFN();
  161.     }

  162.     public String getLastName() {
  163.         return lastName;
  164.     }

  165.     public void setLastName(String lastName) {
  166.         this.lastName = lastName;
  167.         // Update FN field
  168.         updateFN();
  169.     }

  170.     public String getMiddleName() {
  171.         return middleName;
  172.     }

  173.     public void setMiddleName(String middleName) {
  174.         this.middleName = middleName;
  175.         // Update FN field
  176.         updateFN();
  177.     }

  178.     public String getNickName() {
  179.         return otherSimpleFields.get("NICKNAME");
  180.     }

  181.     public void setNickName(String nickName) {
  182.         otherSimpleFields.put("NICKNAME", nickName);
  183.     }

  184.     public String getEmailHome() {
  185.         return emailHome;
  186.     }

  187.     public void setEmailHome(String email) {
  188.         this.emailHome = email;
  189.     }

  190.     public String getEmailWork() {
  191.         return emailWork;
  192.     }

  193.     public void setEmailWork(String emailWork) {
  194.         this.emailWork = emailWork;
  195.     }

  196.     public String getJabberId() {
  197.         return otherSimpleFields.get("JABBERID");
  198.     }

  199.     public void setJabberId(String jabberId) {
  200.         otherSimpleFields.put("JABBERID", jabberId);
  201.     }

  202.     public String getOrganization() {
  203.         return organization;
  204.     }

  205.     public void setOrganization(String organization) {
  206.         this.organization = organization;
  207.     }

  208.     public String getOrganizationUnit() {
  209.         return organizationUnit;
  210.     }

  211.     public void setOrganizationUnit(String organizationUnit) {
  212.         this.organizationUnit = organizationUnit;
  213.     }

  214.     /**
  215.      * Get home address field
  216.      *
  217.      * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET,
  218.      *                  LOCALITY, REGION, PCODE, CTRY
  219.      */
  220.     public String getAddressFieldHome(String addrField) {
  221.         return homeAddr.get(addrField);
  222.     }

  223.     /**
  224.      * Set home address field
  225.      *
  226.      * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET,
  227.      *                  LOCALITY, REGION, PCODE, CTRY
  228.      */
  229.     public void setAddressFieldHome(String addrField, String value) {
  230.         homeAddr.put(addrField, value);
  231.     }

  232.     /**
  233.      * Get work address field
  234.      *
  235.      * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET,
  236.      *                  LOCALITY, REGION, PCODE, CTRY
  237.      */
  238.     public String getAddressFieldWork(String addrField) {
  239.         return workAddr.get(addrField);
  240.     }

  241.     /**
  242.      * Set work address field
  243.      *
  244.      * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET,
  245.      *                  LOCALITY, REGION, PCODE, CTRY
  246.      */
  247.     public void setAddressFieldWork(String addrField, String value) {
  248.         workAddr.put(addrField, value);
  249.     }


  250.     /**
  251.      * Set home phone number
  252.      *
  253.      * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF
  254.      * @param phoneNum  phone number
  255.      */
  256.     public void setPhoneHome(String phoneType, String phoneNum) {
  257.         homePhones.put(phoneType, phoneNum);
  258.     }

  259.     /**
  260.      * Get home phone number
  261.      *
  262.      * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF
  263.      */
  264.     public String getPhoneHome(String phoneType) {
  265.         return homePhones.get(phoneType);
  266.     }

  267.     /**
  268.      * Set work phone number
  269.      *
  270.      * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF
  271.      * @param phoneNum  phone number
  272.      */
  273.     public void setPhoneWork(String phoneType, String phoneNum) {
  274.         workPhones.put(phoneType, phoneNum);
  275.     }

  276.     /**
  277.      * Get work phone number
  278.      *
  279.      * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF
  280.      */
  281.     public String getPhoneWork(String phoneType) {
  282.         return workPhones.get(phoneType);
  283.     }

  284.     /**
  285.      * Set the avatar for the VCard by specifying the url to the image.
  286.      *
  287.      * @param avatarURL the url to the image(png,jpeg,gif,bmp)
  288.      */
  289.     public void setAvatar(URL avatarURL) {
  290.         byte[] bytes = new byte[0];
  291.         try {
  292.             bytes = getBytes(avatarURL);
  293.         }
  294.         catch (IOException e) {
  295.             LOGGER.log(Level.SEVERE, "Error getting bytes from URL: " + avatarURL, e);
  296.         }

  297.         setAvatar(bytes);
  298.     }

  299.     /**
  300.      * Removes the avatar from the vCard
  301.      *
  302.      *  This is done by setting the PHOTO value to the empty string as defined in XEP-0153
  303.      */
  304.     public void removeAvatar() {
  305.         // Remove avatar (if any)
  306.         photoBinval = null;
  307.         photoMimeType = null;
  308.     }

  309.     /**
  310.      * Specify the bytes of the JPEG for the avatar to use.
  311.      * If bytes is null, then the avatar will be removed.
  312.      * 'image/jpeg' will be used as MIME type.
  313.      *
  314.      * @param bytes the bytes of the avatar, or null to remove the avatar data
  315.      */
  316.     public void setAvatar(byte[] bytes) {
  317.         setAvatar(bytes, DEFAULT_MIME_TYPE);
  318.     }

  319.     /**
  320.      * Specify the bytes for the avatar to use as well as the mime type.
  321.      *
  322.      * @param bytes the bytes of the avatar.
  323.      * @param mimeType the mime type of the avatar.
  324.      */
  325.     public void setAvatar(byte[] bytes, String mimeType) {
  326.         // If bytes is null, remove the avatar
  327.         if (bytes == null) {
  328.             removeAvatar();
  329.             return;
  330.         }

  331.         // Otherwise, add to mappings.
  332.         String encodedImage = Base64.encodeToString(bytes);

  333.         setAvatar(encodedImage, mimeType);
  334.     }

  335.     /**
  336.      * Specify the Avatar used for this vCard.
  337.      *
  338.      * @param encodedImage the Base64 encoded image as String
  339.      * @param mimeType the MIME type of the image
  340.      */
  341.     public void setAvatar(String encodedImage, String mimeType) {
  342.         photoBinval = encodedImage;
  343.         photoMimeType = mimeType;
  344.     }

  345.     /**
  346.      * Set the encoded avatar string. This is used by the provider.
  347.      *
  348.      * @param encodedAvatar the encoded avatar string.
  349.      * @deprecated Use {@link #setAvatar(String, String)} instead.
  350.      */
  351.     @Deprecated
  352.     public void setEncodedImage(String encodedAvatar) {
  353.         setAvatar(encodedAvatar, DEFAULT_MIME_TYPE);
  354.     }
  355.    
  356.     /**
  357.      * Return the byte representation of the avatar(if one exists), otherwise returns null if
  358.      * no avatar could be found.
  359.      * <b>Example 1</b>
  360.      * <pre>
  361.      * // Load Avatar from VCard
  362.      * byte[] avatarBytes = vCard.getAvatar();
  363.      * <p/>
  364.      * // To create an ImageIcon for Swing applications
  365.      * ImageIcon icon = new ImageIcon(avatar);
  366.      * <p/>
  367.      * // To create just an image object from the bytes
  368.      * ByteArrayInputStream bais = new ByteArrayInputStream(avatar);
  369.      * try {
  370.      *   Image image = ImageIO.read(bais);
  371.      *  }
  372.      *  catch (IOException e) {
  373.      *    e.printStackTrace();
  374.      * }
  375.      * </pre>
  376.      *
  377.      * @return byte representation of avatar.
  378.      */
  379.     public byte[] getAvatar() {
  380.         if (photoBinval == null) {
  381.             return null;
  382.         }
  383.         return Base64.decode(photoBinval);
  384.     }

  385.     /**
  386.      * Returns the MIME Type of the avatar or null if none is set
  387.      *
  388.      * @return the MIME Type of the avatar or null
  389.      */
  390.     public String getAvatarMimeType() {
  391.         return photoMimeType;
  392.     }

  393.     /**
  394.      * Common code for getting the bytes of a url.
  395.      *
  396.      * @param url the url to read.
  397.      */
  398.     public static byte[] getBytes(URL url) throws IOException {
  399.         final String path = url.getPath();
  400.         final File file = new File(path);
  401.         if (file.exists()) {
  402.             return getFileBytes(file);
  403.         }

  404.         return null;
  405.     }

  406.     private static byte[] getFileBytes(File file) throws IOException {
  407.         BufferedInputStream bis = null;
  408.         try {
  409.             bis = new BufferedInputStream(new FileInputStream(file));
  410.             int bytes = (int) file.length();
  411.             byte[] buffer = new byte[bytes];
  412.             int readBytes = bis.read(buffer);
  413.             if (readBytes != buffer.length) {
  414.                 throw new IOException("Entire file not read");
  415.             }
  416.             return buffer;
  417.         }
  418.         finally {
  419.             if (bis != null) {
  420.                 bis.close();
  421.             }
  422.         }
  423.     }

  424.     /**
  425.      * Returns the SHA-1 Hash of the Avatar image.
  426.      *
  427.      * @return the SHA-1 Hash of the Avatar image.
  428.      */
  429.     public String getAvatarHash() {
  430.         byte[] bytes = getAvatar();
  431.         if (bytes == null) {
  432.             return null;
  433.         }

  434.         MessageDigest digest;
  435.         try {
  436.             digest = MessageDigest.getInstance("SHA-1");
  437.         }
  438.         catch (NoSuchAlgorithmException e) {
  439.             LOGGER.log(Level.SEVERE, "Failed to get message digest", e);
  440.             return null;
  441.         }

  442.         digest.update(bytes);
  443.         return StringUtils.encodeHex(digest.digest());
  444.     }

  445.     private void updateFN() {
  446.         StringBuilder sb = new StringBuilder();
  447.         if (firstName != null) {
  448.             sb.append(StringUtils.escapeForXML(firstName)).append(' ');
  449.         }
  450.         if (middleName != null) {
  451.             sb.append(StringUtils.escapeForXML(middleName)).append(' ');
  452.         }
  453.         if (lastName != null) {
  454.             sb.append(StringUtils.escapeForXML(lastName));
  455.         }
  456.         setField("FN", sb.toString());
  457.     }

  458.     /**
  459.      * Save this vCard for the user connected by 'connection'. XMPPConnection should be authenticated
  460.      * and not anonymous.
  461.      *
  462.      * @param connection the XMPPConnection to use.
  463.      * @throws XMPPErrorException thrown if there was an issue setting the VCard in the server.
  464.      * @throws NoResponseException if there was no response from the server.
  465.      * @throws NotConnectedException
  466.      * @throws InterruptedException
  467.      * @deprecated use {@link VCardManager#saveVCard(VCard)} instead.
  468.      */
  469.     @Deprecated
  470.     public void save(XMPPConnection connection) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  471.         VCardManager.getInstanceFor(connection).saveVCard(this);
  472.     }

  473.     /**
  474.      * Load VCard information for a connected user. XMPPConnection should be authenticated
  475.      * and not anonymous.
  476.      * @throws XMPPErrorException
  477.      * @throws NoResponseException
  478.      * @throws NotConnectedException
  479.      * @throws InterruptedException
  480.      * @deprecated use {@link VCardManager#loadVCard()} instead.
  481.      */
  482.     @Deprecated
  483.     public void load(XMPPConnection connection) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
  484.         load(connection, null);
  485.     }

  486.     /**
  487.      * Load VCard information for a given user. XMPPConnection should be authenticated and not anonymous.
  488.      * @throws XMPPErrorException
  489.      * @throws NoResponseException if there was no response from the server.
  490.      * @throws NotConnectedException
  491.      * @throws InterruptedException
  492.      * @deprecated use {@link VCardManager#loadVCard(BareJid)} instead.
  493.      */
  494.     @Deprecated
  495.     public void load(XMPPConnection connection, BareJid user) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  496.         VCard result = VCardManager.getInstanceFor(connection).loadVCard(user);
  497.         copyFieldsFrom(result);
  498.     }

  499.     @Override
  500.     protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
  501.         if (!hasContent()) {
  502.             xml.setEmptyElement();
  503.             return xml;
  504.         }
  505.         xml.rightAngleBracket();
  506.         if (hasNameField()) {
  507.             xml.openElement("N");
  508.             xml.optElement("FAMILY", lastName);
  509.             xml.optElement("GIVEN", firstName);
  510.             xml.optElement("MIDDLE", middleName);
  511.             xml.closeElement("N");
  512.         }
  513.         if (hasOrganizationFields()) {
  514.             xml.openElement("ORG");
  515.             xml.optElement("ORGNAME", organization);
  516.             xml.optElement("ORGUNIT", organizationUnit);
  517.             xml.closeElement("ORG");
  518.         }
  519.         for (Entry<String, String> entry : otherSimpleFields.entrySet()) {
  520.             xml.optElement(entry.getKey(), entry.getValue());
  521.         }
  522.         for (Entry<String, String> entry : otherUnescapableFields.entrySet()) {
  523.             final String value = entry.getValue();
  524.             if (value == null) {
  525.                 continue;
  526.             }
  527.             xml.openElement(entry.getKey());
  528.             xml.append(value);
  529.             xml.closeElement(entry.getKey());
  530.         }
  531.         if (photoBinval != null) {
  532.             xml.openElement("PHOTO");
  533.             xml.escapedElement("BINVAL", photoBinval);
  534.             xml.element("TYPE", photoMimeType);
  535.             xml.closeElement("PHOTO");
  536.         }
  537.         if (emailWork != null) {
  538.             xml.openElement("EMAIL");
  539.             xml.emptyElement("WORK");
  540.             xml.emptyElement("INTERNET");
  541.             xml.emptyElement("PREF");
  542.             xml.element("USERID", emailWork);
  543.             xml.closeElement("EMAIL");
  544.         }
  545.         if (emailHome != null) {
  546.             xml.openElement("EMAIL");
  547.             xml.emptyElement("HOME");
  548.             xml.emptyElement("INTERNET");
  549.             xml.emptyElement("PREF");
  550.             xml.element("USERID", emailHome);
  551.             xml.closeElement("EMAIL");
  552.         }
  553.         for (Entry<String, String> phone : workPhones.entrySet()) {
  554.             final String number = phone.getValue();
  555.             if (number == null) {
  556.                 continue;
  557.             }
  558.             xml.openElement("TEL");
  559.             xml.emptyElement("WORK");
  560.             xml.emptyElement(phone.getKey());
  561.             xml.element("NUMBER", number);
  562.             xml.closeElement("TEL");
  563.         }
  564.         for (Entry<String, String> phone : homePhones.entrySet()) {
  565.             final String number = phone.getValue();
  566.             if (number == null) {
  567.                 continue;
  568.             }
  569.             xml.openElement("TEL");
  570.             xml.emptyElement("HOME");
  571.             xml.emptyElement(phone.getKey());
  572.             xml.element("NUMBER", number);
  573.             xml.closeElement("TEL");
  574.         }
  575.         if (!workAddr.isEmpty()) {
  576.             xml.openElement("ADR");
  577.             xml.emptyElement("WORK");
  578.             for (Entry<String, String> entry : workAddr.entrySet()) {
  579.                 final String value = entry.getValue();
  580.                 if (value == null) {
  581.                     continue;
  582.                 }
  583.                 xml.element(entry.getKey(), value);
  584.             }
  585.             xml.closeElement("ADR");
  586.         }
  587.         if (!homeAddr.isEmpty()) {
  588.             xml.openElement("ADR");
  589.             xml.emptyElement("HOME");
  590.             for (Entry<String, String> entry : homeAddr.entrySet()) {
  591.                 final String value = entry.getValue();
  592.                 if (value == null) {
  593.                     continue;
  594.                 }
  595.                 xml.element(entry.getKey(), value);
  596.             }
  597.             xml.closeElement("ADR");
  598.         }
  599.         return xml;
  600.     }

  601.     private void copyFieldsFrom(VCard from) {
  602.         Field[] fields = VCard.class.getDeclaredFields();
  603.         for (Field field : fields) {
  604.             if (field.getDeclaringClass() == VCard.class &&
  605.                     !Modifier.isFinal(field.getModifiers())) {
  606.                 try {
  607.                     field.setAccessible(true);
  608.                     field.set(this, field.get(from));
  609.                 }
  610.                 catch (IllegalAccessException e) {
  611.                     throw new RuntimeException("This cannot happen:" + field, e);
  612.                 }
  613.             }
  614.         }
  615.     }

  616.     private boolean hasContent() {
  617.         //noinspection OverlyComplexBooleanExpression
  618.         return hasNameField()
  619.                 || hasOrganizationFields()
  620.                 || emailHome != null
  621.                 || emailWork != null
  622.                 || otherSimpleFields.size() > 0
  623.                 || otherUnescapableFields.size() > 0
  624.                 || homeAddr.size() > 0
  625.                 || homePhones.size() > 0
  626.                 || workAddr.size() > 0
  627.                 || workPhones.size() > 0
  628.                 || photoBinval != null
  629.                 ;
  630.     }

  631.     private boolean hasNameField() {
  632.         return firstName != null || lastName != null || middleName != null;
  633.     }

  634.     private boolean hasOrganizationFields() {
  635.         return organization != null || organizationUnit != null;
  636.     }

  637.     // Used in tests:

  638.     public boolean equals(Object o) {
  639.         if (this == o) return true;
  640.         if (o == null || getClass() != o.getClass()) return false;

  641.         final VCard vCard = (VCard) o;

  642.         if (emailHome != null ? !emailHome.equals(vCard.emailHome) : vCard.emailHome != null) {
  643.             return false;
  644.         }
  645.         if (emailWork != null ? !emailWork.equals(vCard.emailWork) : vCard.emailWork != null) {
  646.             return false;
  647.         }
  648.         if (firstName != null ? !firstName.equals(vCard.firstName) : vCard.firstName != null) {
  649.             return false;
  650.         }
  651.         if (!homeAddr.equals(vCard.homeAddr)) {
  652.             return false;
  653.         }
  654.         if (!homePhones.equals(vCard.homePhones)) {
  655.             return false;
  656.         }
  657.         if (lastName != null ? !lastName.equals(vCard.lastName) : vCard.lastName != null) {
  658.             return false;
  659.         }
  660.         if (middleName != null ? !middleName.equals(vCard.middleName) : vCard.middleName != null) {
  661.             return false;
  662.         }
  663.         if (organization != null ?
  664.                 !organization.equals(vCard.organization) : vCard.organization != null) {
  665.             return false;
  666.         }
  667.         if (organizationUnit != null ?
  668.                 !organizationUnit.equals(vCard.organizationUnit) : vCard.organizationUnit != null) {
  669.             return false;
  670.         }
  671.         if (!otherSimpleFields.equals(vCard.otherSimpleFields)) {
  672.             return false;
  673.         }
  674.         if (!workAddr.equals(vCard.workAddr)) {
  675.             return false;
  676.         }
  677.         if (photoBinval != null ? !photoBinval.equals(vCard.photoBinval) : vCard.photoBinval != null) {
  678.             return false;
  679.         }

  680.         return workPhones.equals(vCard.workPhones);
  681.     }

  682.     public int hashCode() {
  683.         int result;
  684.         result = homePhones.hashCode();
  685.         result = 29 * result + workPhones.hashCode();
  686.         result = 29 * result + homeAddr.hashCode();
  687.         result = 29 * result + workAddr.hashCode();
  688.         result = 29 * result + (firstName != null ? firstName.hashCode() : 0);
  689.         result = 29 * result + (lastName != null ? lastName.hashCode() : 0);
  690.         result = 29 * result + (middleName != null ? middleName.hashCode() : 0);
  691.         result = 29 * result + (emailHome != null ? emailHome.hashCode() : 0);
  692.         result = 29 * result + (emailWork != null ? emailWork.hashCode() : 0);
  693.         result = 29 * result + (organization != null ? organization.hashCode() : 0);
  694.         result = 29 * result + (organizationUnit != null ? organizationUnit.hashCode() : 0);
  695.         result = 29 * result + otherSimpleFields.hashCode();
  696.         result = 29 * result + (photoBinval != null ? photoBinval.hashCode() : 0);
  697.         return result;
  698.     }

  699. }