Java tutorial
/******************************************************************************* * Copyright (c) 2014 Gerry Healy <nickel_chrome@mac.com> * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html * * Based on DavDroid: * Richard Hirner (bitfire web engineering) * * Contributors: * Gerry Healy <nickel_chrome@mac.com> - Initial implementation ******************************************************************************/ package org.exfio.weavedroid.resource; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.io.StringReader; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import lombok.Getter; import lombok.Setter; import lombok.ToString; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import android.util.Log; import ezvcard.Ezvcard; import ezvcard.VCard; import ezvcard.VCardException; import ezvcard.VCardVersion; import ezvcard.parameter.EmailType; import ezvcard.parameter.ImageType; import ezvcard.parameter.TelephoneType; import ezvcard.property.Address; import ezvcard.property.Anniversary; import ezvcard.property.Birthday; import ezvcard.property.Email; import ezvcard.property.FormattedName; import ezvcard.property.Impp; import ezvcard.property.Logo; import ezvcard.property.Nickname; import ezvcard.property.Note; import ezvcard.property.Organization; import ezvcard.property.Photo; import ezvcard.property.RawProperty; import ezvcard.property.Revision; import ezvcard.property.Role; import ezvcard.property.Sound; import ezvcard.property.Source; import ezvcard.property.StructuredName; import ezvcard.property.Telephone; import ezvcard.property.Title; import ezvcard.property.Uid; import ezvcard.property.Url; import org.exfio.weave.WeaveException; import org.exfio.weave.client.WeaveBasicObject; import org.exfio.weavedroid.Constants; @ToString(callSuper = true) public class Contact extends Resource { private final static String TAG = "weavedroid.Contact"; public final static String PROPERTY_STARRED = "X-WEAVEDROID-STARRED", PROPERTY_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME", PROPERTY_PHONETIC_MIDDLE_NAME = "X-PHONETIC-MIDDLE-NAME", PROPERTY_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME", PROPERTY_SIP = "X-SIP"; public final static EmailType EMAIL_TYPE_MOBILE = EmailType.get("X-MOBILE"); public final static TelephoneType PHONE_TYPE_CALLBACK = TelephoneType.get("X-CALLBACK"), PHONE_TYPE_COMPANY_MAIN = TelephoneType.get("X-COMPANY_MAIN"), PHONE_TYPE_RADIO = TelephoneType.get("X-RADIO"), PHONE_TYPE_ASSISTANT = TelephoneType.get("X-ASSISTANT"), PHONE_TYPE_MMS = TelephoneType.get("X-MMS"); @Getter @Setter private String unknownProperties; @Getter @Setter private boolean starred; @Getter @Setter private String displayName, nickName; @Getter @Setter private String prefix, givenName, middleName, familyName, suffix; @Getter @Setter private String phoneticGivenName, phoneticMiddleName, phoneticFamilyName; @Getter @Setter private String note; @Getter @Setter private Organization organization; @Getter @Setter private String jobTitle, jobDescription; @Getter @Setter private byte[] photo; @Getter @Setter private Anniversary anniversary; @Getter @Setter private Birthday birthDay; @Getter private List<Telephone> phoneNumbers = new LinkedList<Telephone>(); @Getter private List<Email> emails = new LinkedList<Email>(); @Getter private List<Impp> impps = new LinkedList<Impp>(); @Getter private List<Address> addresses = new LinkedList<Address>(); @Getter private List<String> URLs = new LinkedList<String>(); /* instance methods */ public Contact(String id, String ETag) { super(id, ETag); } public Contact(long localID, String id, String ETag) { super(localID, id, ETag); } public static Contact fromWeaveBasicObject(WeaveBasicObject wbo) throws WeaveException { Log.d(TAG, "fromWeaveBasicObject()"); Contact con = new Contact(wbo.getId(), wbo.getModified().toString()); try { InputStream is = new ByteArrayInputStream(wbo.getPayload().getBytes("UTF-8")); con.parseJCard(is); } catch (UnsupportedEncodingException e) { throw new WeaveException(e); } catch (IOException e) { throw new WeaveException(e); } return con; } public void parseJCard(InputStream is) throws IOException { Log.d(TAG, "parseJCard()"); //Log.d(TAG, "JSON: " + IOUtils.toString(is)); List<List<String>> warnings = new LinkedList<List<String>>(); VCard vcard = Ezvcard.parseJson(is).warnings(warnings).first(); Log.d(TAG, "Num EzVCard parse warnings: " + warnings.get(0).size()); if (warnings.get(0).size() > 0) { Log.w(TAG, "EzVCard parse warnings"); Iterator<String> iter = warnings.get(0).listIterator(); while (iter.hasNext()) { Log.w(TAG, iter.next()); } } if (vcard == null) return; Log.d(TAG, "parsed JCard:\n" + Ezvcard.write(vcard).version(VCardVersion.V3_0).versionStrict(false).prodId(false).go()); fromVCardObject(vcard); } public void parseVCard(InputStream is) throws IOException, VCardException { Log.d(TAG, "parseVCard()"); VCard vcard = Ezvcard.parse(is).first(); if (vcard == null) return; fromVCardObject(vcard); } protected void fromVCardObject(VCard vcard) throws IOException, VCardException { Log.d(TAG, "fromVCardObject()"); // now work through all supported properties // supported properties are removed from the VCard after parsing // so that only unknown properties are left and can be stored separately // UID Uid uid = vcard.getUid(); if (uid != null) { this.uid = uid.getValue(); vcard.removeProperties(Uid.class); } else { Log.w(TAG, "Received VCard without UID, generating new one"); generateUID(); } // X-DAVDROID-STARRED RawProperty starred = vcard.getExtendedProperty(PROPERTY_STARRED); if (starred != null && starred.getValue() != null) { this.starred = starred.getValue().equals("1"); vcard.removeExtendedProperty(PROPERTY_STARRED); } else this.starred = false; // FN FormattedName fn = vcard.getFormattedName(); if (fn != null) { displayName = fn.getValue(); vcard.removeProperties(FormattedName.class); } else Log.w(TAG, "Received invalid VCard without FN (formatted name) property"); // N StructuredName n = vcard.getStructuredName(); if (n != null) { prefix = StringUtils.join(n.getPrefixes(), " "); givenName = n.getGiven(); middleName = StringUtils.join(n.getAdditional(), " "); familyName = n.getFamily(); suffix = StringUtils.join(n.getSuffixes(), " "); vcard.removeProperties(StructuredName.class); } // phonetic names RawProperty phoneticFirstName = vcard.getExtendedProperty(PROPERTY_PHONETIC_FIRST_NAME), phoneticMiddleName = vcard.getExtendedProperty(PROPERTY_PHONETIC_MIDDLE_NAME), phoneticLastName = vcard.getExtendedProperty(PROPERTY_PHONETIC_LAST_NAME); if (phoneticFirstName != null) { phoneticGivenName = phoneticFirstName.getValue(); vcard.removeExtendedProperty(PROPERTY_PHONETIC_FIRST_NAME); } if (phoneticMiddleName != null) { this.phoneticMiddleName = phoneticMiddleName.getValue(); vcard.removeExtendedProperty(PROPERTY_PHONETIC_MIDDLE_NAME); } if (phoneticLastName != null) { phoneticFamilyName = phoneticLastName.getValue(); vcard.removeExtendedProperty(PROPERTY_PHONETIC_LAST_NAME); } // TEL phoneNumbers = vcard.getTelephoneNumbers(); vcard.removeProperties(Telephone.class); // EMAIL emails = vcard.getEmails(); vcard.removeProperties(Email.class); // PHOTO for (Photo photo : vcard.getPhotos()) { this.photo = photo.getData(); vcard.removeProperties(Photo.class); break; } // ORG organization = vcard.getOrganization(); vcard.removeProperties(Organization.class); // TITLE for (Title title : vcard.getTitles()) { jobTitle = title.getValue(); vcard.removeProperties(Title.class); break; } // ROLE for (Role role : vcard.getRoles()) { this.jobDescription = role.getValue(); vcard.removeProperties(Role.class); break; } // IMPP impps = vcard.getImpps(); vcard.removeProperties(Impp.class); // NICKNAME Nickname nicknames = vcard.getNickname(); if (nicknames != null) { if (nicknames.getValues() != null) nickName = StringUtils.join(nicknames.getValues(), ", "); vcard.removeProperties(Nickname.class); } // NOTE List<String> notes = new LinkedList<String>(); for (Note note : vcard.getNotes()) notes.add(note.getValue()); if (!notes.isEmpty()) note = StringUtils.join(notes, "\n---\n"); vcard.removeProperties(Note.class); // ADR addresses = vcard.getAddresses(); vcard.removeProperties(Address.class); // URL for (Url url : vcard.getUrls()) URLs.add(url.getValue()); vcard.removeProperties(Url.class); // BDAY birthDay = vcard.getBirthday(); vcard.removeProperties(Birthday.class); // ANNIVERSARY anniversary = vcard.getAnniversary(); vcard.removeProperties(Anniversary.class); // X-SIP for (RawProperty sip : vcard.getExtendedProperties(PROPERTY_SIP)) impps.add(new Impp("sip", sip.getValue())); vcard.removeExtendedProperty(PROPERTY_SIP); // remove binary properties because of potential OutOfMemory / TransactionTooLarge exceptions vcard.removeProperties(Logo.class); vcard.removeProperties(Sound.class); // remove properties that don't apply anymore vcard.removeProperties(Revision.class); vcard.removeProperties(Source.class); // store all remaining properties into unknownProperties if (!vcard.getProperties().isEmpty() || !vcard.getExtendedProperties().isEmpty()) unknownProperties = vcard.write(); else unknownProperties = null; } public static WeaveBasicObject toWeaveBasicObject(Resource res) throws WeaveException { return ((Contact) res).toWeaveBasicObject(); } public WeaveBasicObject toWeaveBasicObject() throws WeaveException { WeaveBasicObject wbo = new WeaveBasicObject(this.getId(), null, null, null, this.writeJCard()); return wbo; } public String writeJCard() throws WeaveException { VCard vcard = toVCardObject(); return Ezvcard.writeJson(vcard).versionStrict(false).prodId(false).go(); } public String writeVCard() throws WeaveException { VCard vcard = toVCardObject(); return Ezvcard.write(vcard).version(VCardVersion.V3_0).versionStrict(false).prodId(false).go(); } protected VCard toVCardObject() { VCard vcard = null; try { if (unknownProperties != null) vcard = Ezvcard.parse(unknownProperties).first(); } catch (Exception e) { Log.w(TAG, "Couldn't parse original property set, beginning from scratch"); } if (vcard == null) vcard = new VCard(); if (uid != null) vcard.setUid(new Uid(uid)); else Log.wtf(TAG, "Generating VCard without UID"); if (starred) vcard.setExtendedProperty(PROPERTY_STARRED, "1"); if (displayName != null) vcard.setFormattedName(displayName); else if (organization != null && organization.getValues() != null && organization.getValues().get(0) != null) vcard.setFormattedName(organization.getValues().get(0)); else Log.w(TAG, "No FN (formatted name) available to generate VCard"); // N if (familyName != null || middleName != null || givenName != null) { StructuredName n = new StructuredName(); if (prefix != null) for (String p : StringUtils.split(prefix)) n.addPrefix(p); n.setGiven(givenName); if (middleName != null) for (String middle : StringUtils.split(middleName)) n.addAdditional(middle); n.setFamily(familyName); if (suffix != null) for (String s : StringUtils.split(suffix)) n.addSuffix(s); vcard.setStructuredName(n); } // phonetic names if (phoneticGivenName != null) vcard.addExtendedProperty(PROPERTY_PHONETIC_FIRST_NAME, phoneticGivenName); if (phoneticMiddleName != null) vcard.addExtendedProperty(PROPERTY_PHONETIC_MIDDLE_NAME, phoneticMiddleName); if (phoneticFamilyName != null) vcard.addExtendedProperty(PROPERTY_PHONETIC_LAST_NAME, phoneticFamilyName); // TEL for (Telephone phoneNumber : phoneNumbers) vcard.addTelephoneNumber(phoneNumber); // EMAIL for (Email email : emails) vcard.addEmail(email); // ORG, TITLE, ROLE if (organization != null) vcard.setOrganization(organization); if (jobTitle != null) vcard.addTitle(jobTitle); if (jobDescription != null) vcard.addRole(jobDescription); // IMPP for (Impp impp : impps) vcard.addImpp(impp); // NICKNAME if (!StringUtils.isBlank(nickName)) vcard.setNickname(nickName); // NOTE if (!StringUtils.isBlank(note)) vcard.addNote(note); // ADR for (Address address : addresses) vcard.addAddress(address); // URL for (String url : URLs) vcard.addUrl(url); // ANNIVERSARY if (anniversary != null) vcard.setAnniversary(anniversary); // BDAY if (birthDay != null) vcard.setBirthday(birthDay); // PHOTO if (photo != null) vcard.addPhoto(new Photo(photo, ImageType.JPEG)); // PRODID, REV vcard.setProdId("WeaveDroid/" + Constants.APP_VERSION + " (ez-vcard/" + Ezvcard.VERSION + ")"); vcard.setRevision(Revision.now()); return vcard; } }