contacts v3 serializer
Fixed J2ME issues
Fixed Debajit Feedback
Fixed Jen's comments and removed finally 2 obsolete methods
diff --git a/src/com/google/wireless/gdata2/contacts/data/ContactEntry.java b/src/com/google/wireless/gdata2/contacts/data/ContactEntry.java
index a98ee53..e508ae1 100644
--- a/src/com/google/wireless/gdata2/contacts/data/ContactEntry.java
+++ b/src/com/google/wireless/gdata2/contacts/data/ContactEntry.java
@@ -1,4 +1,4 @@
-// Copyright 2007 The Android Open Source Project
+// Copyright 2009 The Android Open Source Project
 
 package com.google.wireless.gdata2.contacts.data;
 
@@ -146,7 +146,7 @@
   /**
    * Accessor to the CalendarLink Collection
    */
-  public Vector getcalendarLinks() {
+  public Vector getCalendarLinks() {
       return calendarLinks;
   }
   
diff --git a/src/com/google/wireless/gdata2/contacts/data/Jot.java b/src/com/google/wireless/gdata2/contacts/data/Jot.java
index 619e2e4..ce896a7 100644
--- a/src/com/google/wireless/gdata2/contacts/data/Jot.java
+++ b/src/com/google/wireless/gdata2/contacts/data/Jot.java
@@ -22,4 +22,10 @@
    * default empty constructor
    */
   public Jot() {}
+
+  /**
+   * override default behaviour, a jot is not relying on either 
+   * label or type 
+   */
+  public void validate() throws ParseException {}
 }
diff --git a/src/com/google/wireless/gdata2/contacts/data/Language.java b/src/com/google/wireless/gdata2/contacts/data/Language.java
index 2b99942..e04c1e4 100644
--- a/src/com/google/wireless/gdata2/contacts/data/Language.java
+++ b/src/com/google/wireless/gdata2/contacts/data/Language.java
@@ -80,10 +80,15 @@
    }
 
   /**
-   * Currently empty, will be filled when the parser is done
-   * 
+   * A Language either has a code or a label, not both
    */
   public void validate() throws ParseException {
+    if ((StringUtils.isEmpty(label) && 
+         StringUtils.isEmpty(code)) || 
+        (!StringUtils.isEmpty(label) && 
+         !StringUtils.isEmpty(code))) {
+      throw new ParseException("exactly one of label or code must be set");
+    }
   }
 }
 
diff --git a/src/com/google/wireless/gdata2/contacts/data/StructuredPostalAddress.java b/src/com/google/wireless/gdata2/contacts/data/StructuredPostalAddress.java
index 3b3e5f5..49695c1 100644
--- a/src/com/google/wireless/gdata2/contacts/data/StructuredPostalAddress.java
+++ b/src/com/google/wireless/gdata2/contacts/data/StructuredPostalAddress.java
@@ -21,7 +21,6 @@
   private String pobox;
   private String neighborhood;
   private String city;
-  private String subregion;
   private String region;
   private String postcode;
   private String country;
@@ -110,25 +109,6 @@
     this.city = city;
   }
 
-  /**
-   * Getter for subregion 
-   * Handles administrative districts such as U.S. or U.K. 
-   * counties that are not used for mail addressing purposes. 
-   * Subregion is not intended for delivery addresses. 
-   */
-  public String getSubregion() {
-      return this.subregion;
-  }
-  
-  /**
-   * Setter for subregion 
-   * Handles administrative districts such as U.S. or U.K. 
-   * counties that are not used for mail addressing purposes. 
-   * Subregion is not intended for delivery addresses. 
-   */
-  public void setSubregion(String subregion) {
-    this.subregion = subregion;
-  }
 
   /**
    * Getter for region 
@@ -205,7 +185,6 @@
     if (pobox != null) sb.append(" pobox:").append(pobox);
     if (neighborhood != null) sb.append(" neighborhood:").append(neighborhood);
     if (city != null) sb.append(" city:").append(city);
-    if (subregion != null) sb.append(" subregion:").append(subregion);
     if (region != null) sb.append(" region:").append(region);
     if (postcode != null) sb.append(" postcode:").append(postcode);
     if (country != null) sb.append(" country:").append(country);
diff --git a/src/com/google/wireless/gdata2/contacts/data/UserDefinedField.java b/src/com/google/wireless/gdata2/contacts/data/UserDefinedField.java
index 2ddb8cb..8696422 100644
--- a/src/com/google/wireless/gdata2/contacts/data/UserDefinedField.java
+++ b/src/com/google/wireless/gdata2/contacts/data/UserDefinedField.java
@@ -85,6 +85,9 @@
    * 
    */
   public void validate() throws ParseException {
+    if (StringUtils.isEmpty(key)) {
+      throw new ParseException("key has to be set");
+    }
   }
 }
 
diff --git a/src/com/google/wireless/gdata2/contacts/parser/xml/XmlContactsGDataParser.java b/src/com/google/wireless/gdata2/contacts/parser/xml/XmlContactsGDataParser.java
index 3020716..cc82148 100644
--- a/src/com/google/wireless/gdata2/contacts/parser/xml/XmlContactsGDataParser.java
+++ b/src/com/google/wireless/gdata2/contacts/parser/xml/XmlContactsGDataParser.java
@@ -501,7 +501,7 @@
   private static void parseContactsElement(ContactsElement element, XmlPullParser parser,
       Hashtable relToTypeMap) throws XmlPullParserException {
     parseTypedElement(element, parser, relToTypeMap);
-    element.setIsPrimary("true".equals(parser.getAttributeValue(null  /* ns */, "primary")));
+    element.setIsPrimary("true".equals(parser.getAttributeValue(null  /* ns */, XmlNametable.PRIMARY)));
   }
 
   private static void parseTypedElement(TypedElement element, XmlPullParser parser,
@@ -578,7 +578,7 @@
       while (true) {
         String tag = XmlUtils.nextDirectChildTag(parser, depth);
         if (tag == null) break;
-        if (XmlNametable.GD_NAME.equals(tag)) {
+        if (XmlNametable.GD_NAME_GIVENNAME.equals(tag)) {
           element.setGivenName(XmlUtils.extractChildText(parser));
         } else if (XmlNametable.GD_NAME_ADDITIONALNAME.equals(tag)) {
           element.setAdditionalNameYomi(
diff --git a/src/com/google/wireless/gdata2/contacts/parser/xml/XmlNametable.java b/src/com/google/wireless/gdata2/contacts/parser/xml/XmlNametable.java
index b7c7d03..44c3219 100644
--- a/src/com/google/wireless/gdata2/contacts/parser/xml/XmlNametable.java
+++ b/src/com/google/wireless/gdata2/contacts/parser/xml/XmlNametable.java
@@ -19,6 +19,7 @@
   public static String ETAG = "etag";
   public static String VALUESTRING = "valueString";
   public static String STARTTIME = "startTime";
+  public static String PRIMARY = "primary";
 
   // GD namespace
   public static String GD_EMAIL = "email";
@@ -28,6 +29,7 @@
   public static String GD_IM = "im";
   public static String GD_DELETED = "deleted";
   public static String GD_NAME = "name";
+  public static String GD_NAME_GIVENNAME = "givenName";
   public static String GD_NAME_ADDITIONALNAME = "additionalName";
   public static String GD_NAME_YOMI = "yomi";
   public static String GD_NAME_FAMILYNAME = "familyName";
@@ -78,4 +80,4 @@
   public static String GC_SUBJECT = "subject";
   public static String GC_UDF = "userDefinedField";
   public static String GC_WEBSITE ="website";
-}
+}
\ No newline at end of file
diff --git a/src/com/google/wireless/gdata2/contacts/serializer/xml/XmlContactEntryGDataSerializer.java b/src/com/google/wireless/gdata2/contacts/serializer/xml/XmlContactEntryGDataSerializer.java
index 4646490..ead1352 100644
--- a/src/com/google/wireless/gdata2/contacts/serializer/xml/XmlContactEntryGDataSerializer.java
+++ b/src/com/google/wireless/gdata2/contacts/serializer/xml/XmlContactEntryGDataSerializer.java
@@ -1,29 +1,41 @@
-// Copyright 2007 The Android Open Source Project
+// Copyright 2009 The Android Open Source Project
 
 package com.google.wireless.gdata2.contacts.serializer.xml;
 
+import com.google.wireless.gdata2.contacts.data.CalendarLink;
 import com.google.wireless.gdata2.contacts.data.ContactEntry;
 import com.google.wireless.gdata2.contacts.data.ContactsElement;
 import com.google.wireless.gdata2.contacts.data.EmailAddress;
+import com.google.wireless.gdata2.contacts.data.Event;
+import com.google.wireless.gdata2.contacts.data.ExternalId;
+import com.google.wireless.gdata2.contacts.data.GroupMembershipInfo;
 import com.google.wireless.gdata2.contacts.data.ImAddress;
+import com.google.wireless.gdata2.contacts.data.Jot;
+import com.google.wireless.gdata2.contacts.data.Language;
+import com.google.wireless.gdata2.contacts.data.Name;
 import com.google.wireless.gdata2.contacts.data.Organization;
 import com.google.wireless.gdata2.contacts.data.PhoneNumber;
+import com.google.wireless.gdata2.contacts.data.Relation;
 import com.google.wireless.gdata2.contacts.data.StructuredPostalAddress;
-import com.google.wireless.gdata2.contacts.data.GroupMembershipInfo;
+import com.google.wireless.gdata2.contacts.data.TypedElement;
+import com.google.wireless.gdata2.contacts.data.UserDefinedField;
+import com.google.wireless.gdata2.contacts.data.WebSite;
 import com.google.wireless.gdata2.contacts.parser.xml.XmlContactsGDataParser;
-import com.google.wireless.gdata2.data.StringUtils;
+import com.google.wireless.gdata2.contacts.parser.xml.XmlNametable;
 import com.google.wireless.gdata2.data.ExtendedProperty;
+import com.google.wireless.gdata2.data.StringUtils;
 import com.google.wireless.gdata2.parser.ParseException;
 import com.google.wireless.gdata2.parser.xml.XmlGDataParser;
 import com.google.wireless.gdata2.parser.xml.XmlParserFactory;
 import com.google.wireless.gdata2.serializer.xml.XmlEntryGDataSerializer;
 
-import org.xmlpull.v1.XmlSerializer;
-
 import java.io.IOException;
 import java.util.Enumeration;
 import java.util.Hashtable;
 
+import org.xmlpull.v1.XmlSerializer;
+
+
 /**
  *  Serializes Google Contact entries into the Atom XML format.
  */
@@ -54,9 +66,6 @@
     serializeLink(serializer, XmlContactsGDataParser.LINK_REL_PHOTO,
         entry.getLinkPhotoHref(), entry.getLinkPhotoType(), entry.getLinkPhotoETag());
 
-    // Serialize the contact specific parts of this entry.  Note that
-    // gd:ContactSection and gd:geoPt are likely to be deprecated, and
-    // are not currently serialized.
     Enumeration eachEmail = entry.getEmailAddresses().elements();
     while (eachEmail.hasMoreElements()) {
       serialize(serializer, (EmailAddress) eachEmail.nextElement());
@@ -91,24 +100,84 @@
     while (eachGroup.hasMoreElements()) {
       serialize(serializer, (GroupMembershipInfo) eachGroup.nextElement());
     }
+
+    Enumeration eachCalendar = entry.getCalendarLinks().elements();
+    while (eachCalendar.hasMoreElements()) {
+      serialize(serializer, (CalendarLink) eachCalendar.nextElement());
+    }
+
+    Enumeration eachEvent = entry.getEvents().elements();
+    while (eachEvent.hasMoreElements()) {
+      serialize(serializer, (Event) eachEvent.nextElement());
+    }
+
+    Enumeration eachExternalId = entry.getExternalIds().elements();
+    while (eachExternalId.hasMoreElements()) {
+      serialize(serializer, (ExternalId) eachExternalId.nextElement());
+    }
+
+    Enumeration eachHobby = entry.getHobbies().elements();
+    while (eachHobby.hasMoreElements()) {
+      serializeHobby(serializer, (String) eachHobby.nextElement());
+    }
+
+    Enumeration eachJot = entry.getJots().elements();
+    while (eachJot.hasMoreElements()) {
+      serialize(serializer, (Jot) eachJot.nextElement());
+    }
+
+    Enumeration eachLanguage = entry.getLanguages().elements();
+    while (eachLanguage.hasMoreElements()) {
+      serialize(serializer, (Language) eachLanguage.nextElement());
+    }
+
+    Enumeration eachRelation = entry.getRelations().elements();
+    while (eachRelation.hasMoreElements()) {
+      serialize(serializer, (Relation) eachRelation.nextElement());
+    }
+
+    Enumeration eachUDF = entry.getUserDefinedFields().elements();
+    while (eachUDF.hasMoreElements()) {
+      serialize(serializer, (UserDefinedField) eachUDF.nextElement());
+    }
+
+    // now serialize simple properties
+
+    serializeElement(serializer, entry.getDirectoryServer(), XmlNametable.GC_DIRECTORYSERVER);
+    serializeElement(serializer, entry.getGender(), XmlNametable.GC_GENDER);
+    serializeElement(serializer, entry.getInitials(), XmlNametable.GC_INITIALS);
+    serializeElement(serializer, entry.getMaidenName(), XmlNametable.GC_MAIDENNAME);
+    serializeElement(serializer, entry.getMileage(), XmlNametable.GC_MILEAGE);
+    serializeElement(serializer, entry.getNickname(), XmlNametable.GC_NICKNAME);
+    serializeElement(serializer, entry.getOccupation(), XmlNametable.GC_OCCUPATION);
+    serializeElement(serializer, entry.getShortName(), XmlNametable.GC_SHORTNAME);
+    serializeElement(serializer, entry.getSubject(), XmlNametable.GC_SUBJECT);
+    serializeElement(serializer, entry.getBirthday(), XmlNametable.GC_BIRTHDAY);
+    serializeElement(serializer, entry.getBillingInformation(), XmlNametable.GC_BILLINGINFO);
+    serializeElement(serializer, entry.getPriority(), 
+                     XmlNametable.GC_PRIORITY, XmlContactsGDataParser.TYPE_TO_REL_PRIORITY);
+    serializeElement(serializer, entry.getSensitivity(), 
+                     XmlNametable.GC_SENSITIVITY, XmlContactsGDataParser.TYPE_TO_REL_SENSITIVITY);
+
+    serializeName(serializer, entry.getName());
   }
 
   private static void serialize(XmlSerializer serializer, EmailAddress email)
       throws IOException, ParseException {
     if (StringUtils.isEmptyOrWhitespace(email.getAddress())) return;
-    serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "email");
+    serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_EMAIL);
     serializeContactsElement(serializer, email, XmlContactsGDataParser.TYPE_TO_REL_EMAIL);
-    serializer.attribute(null /* ns */, "address", email.getAddress());
-    serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "email");
+    serializer.attribute(null /* ns */, XmlNametable.GD_ADDRESS, email.getAddress());
+    serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_EMAIL);
   }
 
   private static void serialize(XmlSerializer serializer, ImAddress im)
       throws IOException, ParseException {
     if (StringUtils.isEmptyOrWhitespace(im.getAddress())) return;
 
-    serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "im");
+    serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_IM);
     serializeContactsElement(serializer, im, XmlContactsGDataParser.TYPE_TO_REL_IM);
-    serializer.attribute(null /* ns */, "address", im.getAddress());
+    serializer.attribute(null /* ns */, XmlNametable.GD_ADDRESS, im.getAddress());
 
     String protocolString;
     switch (im.getProtocolPredefined()) {
@@ -122,82 +191,119 @@
           throw new IllegalArgumentException(
               "the protocol is custom, but the custom string is null");
         }
-        serializer.attribute(null /* ns */, "protocol", protocolString);
+        serializer.attribute(null /* ns */, XmlNametable.GD_PROTOCOL, protocolString);
         break;
 
       default:
         protocolString = (String)XmlContactsGDataParser.IM_PROTOCOL_TYPE_TO_STRING_MAP.get(
             new Byte(im.getProtocolPredefined()));
-        serializer.attribute(null /* ns */, "protocol", protocolString);
+        serializer.attribute(null /* ns */, XmlNametable.GD_PROTOCOL, protocolString);
         break;
     }
 
-    serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "im");
+    serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_IM);
   }
 
   private static void serialize(XmlSerializer serializer, PhoneNumber phone)
       throws IOException, ParseException {
     if (StringUtils.isEmptyOrWhitespace(phone.getPhoneNumber())) return;
-    serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "phoneNumber");
+    serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_PHONENUMBER);
     serializeContactsElement(serializer, phone, XmlContactsGDataParser.TYPE_TO_REL_PHONE);
     serializer.text(phone.getPhoneNumber());
-    serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "phoneNumber");
+    serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_PHONENUMBER);
   }
 
   private static void serialize(XmlSerializer serializer, Organization organization)
       throws IOException, ParseException {
-    final String name = organization.getName();
-    final String title = organization.getTitle();
 
-    if (StringUtils.isEmptyOrWhitespace(name) && StringUtils.isEmptyOrWhitespace(title)) return;
-
-    serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "organization");
+    serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_ORGANIZATION);
     serializeContactsElement(serializer,
             organization, XmlContactsGDataParser.TYPE_TO_REL_ORGANIZATION);
-    if (!StringUtils.isEmpty(name)) {
-      serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "orgName");
-      serializer.text(name);
-      serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "orgName");
+    serializeGDSubelement(serializer, organization.getName(), 
+                          XmlNametable.GD_ORG_NAME);
+    serializeGDSubelement(serializer, organization.getTitle(), 
+                          XmlNametable.GD_ORG_TITLE);
+    serializeGDSubelement(serializer, organization.getOrgDepartment(), 
+                          XmlNametable.GD_ORG_DEPARTMENT);
+    serializeGDSubelement(serializer, organization.getOrgJobDescription(), 
+                          XmlNametable.GD_ORG_JOBDESC);
+    serializeGDSubelement(serializer, organization.getOrgSymbol(), 
+                          XmlNametable.GD_ORG_SYMBOL);
+
+    final String where = organization.getWhere();
+    if (!StringUtils.isEmpty(where)) {
+      serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_WHERE);
+      serializer.attribute(null /* ns */, XmlNametable.VALUESTRING, where);
+      serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_WHERE);
     }
 
-    if (!StringUtils.isEmpty(title)) {
-      serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "orgTitle");
-      serializer.text(title);
-      serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "orgTitle");
-    }
-    serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "organization");
+    serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_ORGANIZATION);
   }
 
+
+  /**
+   * Gets called out of the main serializer loop. Parameters are 
+   * not null.
+   *  
+   * @param serializer 
+   * @param addr 
+   */
   private static void serialize(XmlSerializer serializer, StructuredPostalAddress addr)
       throws IOException, ParseException {
-    // todo: replace this with a real check
-    // if (StringUtils.isEmptyOrWhitespace(addr.getValue())) return;
-    serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "structuredPostalAddress");
+    serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_SPA);
     serializeContactsElement(serializer, addr, XmlContactsGDataParser.TYPE_TO_REL_POSTAL);
-    // todo: need to write serializer code
-    final String addressValue = null;
-    if (addressValue != null) serializer.text(addressValue);
-    serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "postalAddress");
+    serializeGDSubelement(serializer, addr.getStreet(), XmlNametable.GD_SPA_STREET);
+    serializeGDSubelement(serializer, addr.getPobox(), XmlNametable.GD_SPA_POBOX);
+    serializeGDSubelement(serializer, addr.getNeighborhood(), XmlNametable.GD_SPA_NEIGHBORHOOD);
+    serializeGDSubelement(serializer, addr.getCity(), XmlNametable.GD_SPA_CITY);
+    serializeGDSubelement(serializer, addr.getRegion(), XmlNametable.GD_SPA_REGION);
+    serializeGDSubelement(serializer, addr.getPostcode(), XmlNametable.GD_SPA_POSTCODE);
+    serializeGDSubelement(serializer, addr.getCountry(), XmlNametable.GD_SPA_COUNTRY);
+    serializeGDSubelement(serializer, addr.getFormatedAddress(), 
+                          XmlNametable.GD_SPA_FORMATTEDADDRESS);
+    serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_SPA);
+  }
+
+  private static void serializeGDSubelement(XmlSerializer serializer, String value, 
+                                            String elementName) 
+      throws IOException, ParseException {
+    if (StringUtils.isEmpty(value)) return;
+    serializer.startTag(XmlContactsGDataParser.NAMESPACE_GD_URI, elementName);
+    serializer.text(value);
+    serializer.endTag(XmlContactsGDataParser.NAMESPACE_GD_URI, elementName);
+  }
+
+
+  private static void serializeTypedElement(XmlSerializer serializer, TypedElement element, 
+                     Hashtable typeToRelMap) throws IOException, ParseException { 
+
+    final String label = element.getLabel();
+    byte type = element.getType();
+    boolean hasType = type != TypedElement.TYPE_NONE;
+
+    // validate the element
+    element.validate();
+
+    if (label != null) {
+      serializer.attribute(null /* ns */, XmlNametable.LABEL, label);
+    }
+    if (hasType) {
+      serializeRelation(serializer, type, typeToRelMap);
+    }
+  }
+
+  private static void serializeRelation(XmlSerializer serializer, byte type, 
+                     Hashtable typeToRelMap) throws IOException, ParseException { 
+
+    serializer.attribute(null /* ns */, XmlNametable.REL,
+          (String)typeToRelMap.get(new Byte(type)));
   }
 
   private static void serializeContactsElement(XmlSerializer serializer, ContactsElement element,
       Hashtable typeToRelMap) throws IOException, ParseException {
-    final String label = element.getLabel();
-    boolean hasType = element.getType() != ContactsElement.TYPE_NONE;
-
-    if (((label == null) && !hasType) || ((label != null) && hasType)) {
-      throw new ParseException("exactly one of label or rel must be set");
-    }
-
-    if (label != null) {
-      serializer.attribute(null /* ns */, "label", label);
-    }
-    if (hasType) {
-      serializer.attribute(null /* ns */, "rel",
-          (String)typeToRelMap.get(new Byte(element.getType())));
-    }
+    serializeTypedElement(serializer, element, typeToRelMap);
     if (element.isPrimary()) {
-      serializer.attribute(null /* ns */, "primary", "true");
+      serializer.attribute(null /* ns */, XmlNametable.PRIMARY, "true");
     }
   }
 
@@ -210,10 +316,10 @@
       throw new ParseException("the group must not be empty");
     }
 
-    serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, "groupMembershipInfo");
-    serializer.attribute(null /* ns */, "href", group);
-    serializer.attribute(null /* ns */, "deleted", isDeleted ? "true" : "false");
-    serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, "groupMembershipInfo");
+    serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_GMI);
+    serializer.attribute(null /* ns */, XmlNametable.HREF, group);
+    serializer.attribute(null /* ns */, XmlNametable.GD_DELETED, isDeleted ? "true" : "false");
+    serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_GMI);
   }
 
   private static void serialize(XmlSerializer serializer, ExtendedProperty extendedProperty)
@@ -222,17 +328,17 @@
     final String value = extendedProperty.getValue();
     final String xmlBlob = extendedProperty.getXmlBlob();
 
-    serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "extendedProperty");
+    serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_EXTENDEDPROPERTY);
     if (!StringUtils.isEmpty(name)) {
-      serializer.attribute(null /* ns */, "name", name);
+      serializer.attribute(null /* ns */, XmlNametable.GD_NAME, name);
     }
     if (!StringUtils.isEmpty(value)) {
-      serializer.attribute(null /* ns */, "value", value);
+      serializer.attribute(null /* ns */, XmlNametable.VALUE, value);
     }
     if (!StringUtils.isEmpty(xmlBlob)) {
       serializeBlob(serializer, xmlBlob);
     }
-    serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "extendedProperty");
+    serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_EXTENDEDPROPERTY);
   }
 
   private static void serializeBlob(XmlSerializer serializer, String blob)
@@ -240,14 +346,192 @@
      serializer.text(blob);
   }
 
-  private static void serializeYomiName(XmlSerializer serializer,
-      String yomiName)
-      throws IOException {
-    if (StringUtils.isEmpty(yomiName)) {
-      return;
+  /**
+   * takes a typed element and a string value and determines if 
+   * the element and the string together should be serialized. 
+   * if the string is non empty, or the typedelement is worthy of 
+   * serialization, this will return true. 
+   * 
+   * @param element 
+   * @param value 
+   * 
+   * @return boolean 
+   */
+  private static boolean shouldSerialize(TypedElement element, String value)
+  {
+    if (element.getType() != TypedElement.TYPE_NONE) {
+      return true;
     }
-    serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, "yomiName");
-    serializer.text(yomiName);
-    serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, "yomiName");
+    if (!StringUtils.isEmptyOrWhitespace(element.getLabel())) {
+      return true;
+    }
+    if (!StringUtils.isEmptyOrWhitespace(value)) {
+      return true;
+    }
+    return false;
+  }
+
+  private static void serialize(XmlSerializer serializer, CalendarLink calendarLink)
+        throws IOException, ParseException {
+    final String href = calendarLink.getHRef();
+   
+    if (shouldSerialize(calendarLink, href)) {
+      serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, 
+                          XmlNametable.GC_CALENDARLINK);
+      serializeContactsElement(serializer, calendarLink, 
+                               XmlContactsGDataParser.TYPE_TO_REL_CALENDARLINK);
+      if (!StringUtils.isEmpty(href)) {
+        serializer.attribute(null /* ns */, XmlNametable.HREF, href);
+      }
+      serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, 
+                        XmlNametable.GC_CALENDARLINK);
+    }
+  }
+
+  private static void serialize(XmlSerializer serializer, Event event)
+        throws IOException, ParseException {
+    final String startDate = event.getStartDate();
+    if (shouldSerialize(event, startDate)) {
+      serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_EVENT);
+      serializeTypedElement(serializer, event, XmlContactsGDataParser.TYPE_TO_REL_EVENT);
+      if (!StringUtils.isEmpty(startDate)) {
+        serializer.startTag(XmlContactsGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_WHEN);
+        serializer.attribute(null /* ns */, XmlNametable.STARTTIME, startDate);  
+        serializer.endTag(XmlContactsGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_WHEN);     
+      }
+      serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_EVENT);
+    }
+  }
+  
+  private static void serialize(XmlSerializer serializer, ExternalId externalId)
+        throws IOException, ParseException {
+    final String value = externalId.getValue();
+    if (shouldSerialize(externalId, value)) {
+      serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, 
+                          XmlNametable.GC_EXTERNALID);
+      serializeTypedElement(serializer, externalId, 
+                            XmlContactsGDataParser.TYPE_TO_REL_EXTERNALID);
+      if (!StringUtils.isEmpty(value)) {
+        serializer.attribute(null /* ns */, XmlNametable.VALUE, value);  
+      }
+      serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI,  
+                        XmlNametable.GC_EXTERNALID);
+    }
+  }
+  
+  private static void serializeHobby(XmlSerializer serializer, String hobby)
+        throws IOException, ParseException {
+    if (StringUtils.isEmptyOrWhitespace(hobby)) return;
+    serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_HOBBY);
+    serializer.text(hobby);
+    serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_HOBBY);
+  }
+  
+  private static void serialize(XmlSerializer serializer, Jot jot)
+        throws IOException, ParseException {
+    final String value = jot.getLabel();
+
+    if (!StringUtils.isEmptyOrWhitespace(value)) {
+      serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_JOT);
+      serializeRelation(serializer, jot.getType(), XmlContactsGDataParser.TYPE_TO_REL_JOT);
+      serializer.text(value);
+      serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_JOT);
+    }
+  }
+  
+  private static void serialize(XmlSerializer serializer, Language language)
+        throws IOException, ParseException {
+    language.validate();
+    serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_LANGUAGE);
+    final String value = language.getCode();
+    if (!StringUtils.isEmptyOrWhitespace(value)) {
+      serializer.attribute(null /* ns */, XmlNametable.CODE, value); 
+    } else {
+      serializer.attribute(null /* ns */, XmlNametable.LABEL, language.getLabel());      
+    }
+    serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_LANGUAGE);
+  }
+  
+  private static void serialize(XmlSerializer serializer, Relation relation)
+        throws IOException, ParseException {
+    final String value = relation.getText();
+    if (shouldSerialize(relation, value)) {
+      serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_RELATION);
+      serializeTypedElement(serializer, relation, XmlContactsGDataParser.TYPE_TO_REL_RELATION);
+      serializer.text(value);
+      serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_RELATION);
+    }
+  }
+  
+  private static void serialize(XmlSerializer serializer, UserDefinedField udf)
+        throws IOException, ParseException {
+    udf.validate();
+    serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_UDF);
+    serializer.attribute(null /* ns */, XmlNametable.KEY, udf.getKey());
+    serializer.attribute(null /* ns */, XmlNametable.VALUE, udf.getValue());
+    serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_UDF);
+  }
+  
+  private static void serialize(XmlSerializer serializer, WebSite webSite)
+        throws IOException, ParseException {
+    final String href = webSite.getHRef();
+   
+    if (shouldSerialize(webSite, href)) {
+      serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_WEBSITE);
+      serializeContactsElement(serializer, webSite, XmlContactsGDataParser.TYPE_TO_REL_WEBSITE);
+      if (!StringUtils.isEmpty(href)) {
+        serializer.attribute(null /* ns */, XmlNametable.HREF, href);
+      }
+      serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_WEBSITE);
+    }
+  }
+  
+  private static void serializeElement(XmlSerializer serializer, String value, String elementName)
+        throws IOException, ParseException {
+    if (StringUtils.isEmpty(value)) return;
+    serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, elementName);
+    serializer.text(value);
+    serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, elementName);
+  }
+
+  private static void serializeElement(XmlSerializer serializer, byte value, String elementName,
+        Hashtable typeToRelMap) throws IOException, ParseException {
+    if (value == TypedElement.TYPE_NONE) return;
+    serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, elementName);
+    serializeRelation(serializer, value, typeToRelMap);
+    serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, elementName);
+  }
+
+  private static void serializeName(XmlSerializer serializer, Name name)
+        throws IOException, ParseException {
+
+    serializer.startTag(XmlContactsGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_NAME);
+    serializeNameSubelement(serializer, name.getGivenName(), 
+                            name.getGivenNameYomi(), XmlNametable.GD_NAME_GIVENNAME);
+    serializeNameSubelement(serializer, name.getAdditionalName(), 
+                            name.getAdditionalNameYomi(), XmlNametable.GD_NAME_ADDITIONALNAME);
+    serializeNameSubelement(serializer, name.getFamilyName(), 
+                            name.getFamilyNameYomi(), XmlNametable.GD_NAME_FAMILYNAME);
+    serializeNameSubelement(serializer, name.getAdditionalName(), 
+                        name.getAdditionalNameYomi(), XmlNametable.GD_NAME_ADDITIONALNAME);
+    serializeNameSubelement(serializer, name.getNamePrefix(), 
+                        null /* yomi */, XmlNametable.GD_NAME_PREFIX);
+    serializeNameSubelement(serializer, name.getNameSuffix(), 
+                        null /* yomi */, XmlNametable.GD_NAME_SUFFIX);
+    serializeNameSubelement(serializer, name.getFullName(), 
+                        null /* yomi */, XmlNametable.GD_NAME_FULLNAME);
+    serializer.endTag(XmlContactsGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_NAME);
+  }
+
+  private static void serializeNameSubelement(XmlSerializer serializer, String value, 
+                                              String yomi, String elementName) 
+        throws IOException, ParseException {
+    if (StringUtils.isEmpty(value)) return;
+    serializer.startTag(XmlContactsGDataParser.NAMESPACE_GD_URI, elementName);
+    if (!StringUtils.isEmpty(yomi)) {
+      serializer.attribute(null /* ns */, XmlNametable.GD_NAME_YOMI, yomi);
+    }
+    serializer.text(value);
+    serializer.endTag(XmlContactsGDataParser.NAMESPACE_GD_URI, elementName);
   }
 }