summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:29:31 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:29:31 -0800
commit65b014e752df14505170881528f22a88f8922835 (patch)
tree62788164cfefb3e80e799e08f3e50e4ea876e9cc
parent7e245e06f7aad19e0f66075da657ef49d194b1bd (diff)
downloadgdata-65b014e752df14505170881528f22a88f8922835.tar.gz
auto import from //depot/cupcake/@135843
-rw-r--r--src/com/google/wireless/gdata/GDataException.java64
-rw-r--r--src/com/google/wireless/gdata/calendar/client/CalendarClient.java96
-rw-r--r--src/com/google/wireless/gdata/calendar/client/package.html5
-rw-r--r--src/com/google/wireless/gdata/calendar/data/CalendarEntry.java161
-rw-r--r--src/com/google/wireless/gdata/calendar/data/CalendarsFeed.java18
-rw-r--r--src/com/google/wireless/gdata/calendar/data/EventEntry.java315
-rw-r--r--src/com/google/wireless/gdata/calendar/data/EventsFeed.java33
-rw-r--r--src/com/google/wireless/gdata/calendar/data/Recurrence.java28
-rw-r--r--src/com/google/wireless/gdata/calendar/data/Reminder.java92
-rw-r--r--src/com/google/wireless/gdata/calendar/data/When.java53
-rw-r--r--src/com/google/wireless/gdata/calendar/data/Who.java153
-rw-r--r--src/com/google/wireless/gdata/calendar/data/package.html5
-rw-r--r--src/com/google/wireless/gdata/calendar/package.html5
-rw-r--r--src/com/google/wireless/gdata/calendar/parser/package.html5
-rw-r--r--src/com/google/wireless/gdata/calendar/parser/xml/XmlCalendarGDataParserFactory.java98
-rw-r--r--src/com/google/wireless/gdata/calendar/parser/xml/XmlCalendarsGDataParser.java136
-rw-r--r--src/com/google/wireless/gdata/calendar/parser/xml/XmlEventsGDataParser.java426
-rw-r--r--src/com/google/wireless/gdata/calendar/parser/xml/package.html5
-rw-r--r--src/com/google/wireless/gdata/calendar/serializer/package.html5
-rw-r--r--src/com/google/wireless/gdata/calendar/serializer/xml/XmlEventEntryGDataSerializer.java399
-rw-r--r--src/com/google/wireless/gdata/calendar/serializer/xml/package.html5
-rw-r--r--src/com/google/wireless/gdata/client/GDataClient.java149
-rw-r--r--src/com/google/wireless/gdata/client/GDataParserFactory.java48
-rw-r--r--src/com/google/wireless/gdata/client/GDataServiceClient.java209
-rw-r--r--src/com/google/wireless/gdata/client/HttpException.java58
-rw-r--r--src/com/google/wireless/gdata/client/HttpQueryParams.java73
-rw-r--r--src/com/google/wireless/gdata/client/QueryParams.java240
-rw-r--r--src/com/google/wireless/gdata/client/package.html5
-rw-r--r--src/com/google/wireless/gdata/contacts/client/ContactsClient.java33
-rw-r--r--src/com/google/wireless/gdata/contacts/client/package.html5
-rw-r--r--src/com/google/wireless/gdata/contacts/data/ContactEntry.java218
-rw-r--r--src/com/google/wireless/gdata/contacts/data/ContactsElement.java71
-rw-r--r--src/com/google/wireless/gdata/contacts/data/ContactsFeed.java16
-rw-r--r--src/com/google/wireless/gdata/contacts/data/EmailAddress.java28
-rw-r--r--src/com/google/wireless/gdata/contacts/data/GeoPt.java70
-rw-r--r--src/com/google/wireless/gdata/contacts/data/GroupEntry.java39
-rw-r--r--src/com/google/wireless/gdata/contacts/data/GroupMembershipInfo.java38
-rw-r--r--src/com/google/wireless/gdata/contacts/data/GroupsFeed.java14
-rw-r--r--src/com/google/wireless/gdata/contacts/data/ImAddress.java59
-rw-r--r--src/com/google/wireless/gdata/contacts/data/Organization.java43
-rw-r--r--src/com/google/wireless/gdata/contacts/data/PhoneNumber.java33
-rw-r--r--src/com/google/wireless/gdata/contacts/data/PostalAddress.java28
-rw-r--r--src/com/google/wireless/gdata/contacts/data/package.html5
-rw-r--r--src/com/google/wireless/gdata/contacts/package.html5
-rw-r--r--src/com/google/wireless/gdata/contacts/parser/package.html5
-rw-r--r--src/com/google/wireless/gdata/contacts/parser/xml/XmlContactsGDataParser.java302
-rw-r--r--src/com/google/wireless/gdata/contacts/parser/xml/XmlContactsGDataParserFactory.java125
-rw-r--r--src/com/google/wireless/gdata/contacts/parser/xml/XmlGroupEntryGDataParser.java60
-rw-r--r--src/com/google/wireless/gdata/contacts/parser/xml/package.html5
-rw-r--r--src/com/google/wireless/gdata/contacts/serializer/package.html5
-rw-r--r--src/com/google/wireless/gdata/contacts/serializer/xml/XmlContactEntryGDataSerializer.java243
-rw-r--r--src/com/google/wireless/gdata/contacts/serializer/xml/XmlGroupEntryGDataSerializer.java53
-rw-r--r--src/com/google/wireless/gdata/contacts/serializer/xml/package.html5
-rw-r--r--src/com/google/wireless/gdata/data/Entry.java291
-rw-r--r--src/com/google/wireless/gdata/data/ExtendedProperty.java53
-rw-r--r--src/com/google/wireless/gdata/data/Feed.java122
-rw-r--r--src/com/google/wireless/gdata/data/MediaEntry.java10
-rw-r--r--src/com/google/wireless/gdata/data/StringUtils.java54
-rw-r--r--src/com/google/wireless/gdata/data/XmlUtils.java133
-rw-r--r--src/com/google/wireless/gdata/data/package.html5
-rw-r--r--src/com/google/wireless/gdata/package.html5
-rw-r--r--src/com/google/wireless/gdata/parser/GDataParser.java65
-rw-r--r--src/com/google/wireless/gdata/parser/ParseException.java38
-rw-r--r--src/com/google/wireless/gdata/parser/package.html5
-rw-r--r--src/com/google/wireless/gdata/parser/xml/SimplePullParser.java300
-rw-r--r--src/com/google/wireless/gdata/parser/xml/XmlGDataParser.java530
-rw-r--r--src/com/google/wireless/gdata/parser/xml/XmlMediaEntryGDataParser.java42
-rw-r--r--src/com/google/wireless/gdata/parser/xml/XmlParserFactory.java31
-rw-r--r--src/com/google/wireless/gdata/parser/xml/package.html5
-rw-r--r--src/com/google/wireless/gdata/serializer/GDataSerializer.java56
-rw-r--r--src/com/google/wireless/gdata/serializer/package.html5
-rw-r--r--src/com/google/wireless/gdata/serializer/xml/XmlEntryGDataSerializer.java268
-rw-r--r--src/com/google/wireless/gdata/serializer/xml/package.html5
-rwxr-xr-xsrc/com/google/wireless/gdata/spreadsheets/client/SpreadsheetsClient.java236
-rw-r--r--src/com/google/wireless/gdata/spreadsheets/client/package.html5
-rwxr-xr-xsrc/com/google/wireless/gdata/spreadsheets/data/CellEntry.java134
-rwxr-xr-xsrc/com/google/wireless/gdata/spreadsheets/data/CellFeed.java35
-rwxr-xr-xsrc/com/google/wireless/gdata/spreadsheets/data/ListEntry.java77
-rwxr-xr-xsrc/com/google/wireless/gdata/spreadsheets/data/ListFeed.java35
-rwxr-xr-xsrc/com/google/wireless/gdata/spreadsheets/data/SpreadsheetEntry.java38
-rwxr-xr-xsrc/com/google/wireless/gdata/spreadsheets/data/SpreadsheetFeed.java14
-rw-r--r--src/com/google/wireless/gdata/spreadsheets/data/WorksheetEntry.java105
-rwxr-xr-xsrc/com/google/wireless/gdata/spreadsheets/data/WorksheetFeed.java14
-rw-r--r--src/com/google/wireless/gdata/spreadsheets/data/package.html5
-rw-r--r--src/com/google/wireless/gdata/spreadsheets/package.html5
-rw-r--r--src/com/google/wireless/gdata/spreadsheets/parser/package.html5
-rwxr-xr-xsrc/com/google/wireless/gdata/spreadsheets/parser/xml/XmlCellsGDataParser.java126
-rwxr-xr-xsrc/com/google/wireless/gdata/spreadsheets/parser/xml/XmlListGDataParser.java109
-rwxr-xr-xsrc/com/google/wireless/gdata/spreadsheets/parser/xml/XmlSpreadsheetsGDataParser.java68
-rw-r--r--src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlSpreadsheetsGDataParserFactory.java99
-rwxr-xr-xsrc/com/google/wireless/gdata/spreadsheets/parser/xml/XmlWorksheetsGDataParser.java98
-rw-r--r--src/com/google/wireless/gdata/spreadsheets/parser/xml/package.html5
-rw-r--r--src/com/google/wireless/gdata/spreadsheets/serializer/package.html5
-rwxr-xr-xsrc/com/google/wireless/gdata/spreadsheets/serializer/xml/XmlCellEntryGDataSerializer.java83
-rwxr-xr-xsrc/com/google/wireless/gdata/spreadsheets/serializer/xml/XmlListEntryGDataSerializer.java67
-rw-r--r--src/com/google/wireless/gdata/spreadsheets/serializer/xml/package.html5
-rw-r--r--src/com/google/wireless/gdata/subscribedfeeds/client/SubscribedFeedsClient.java36
-rw-r--r--src/com/google/wireless/gdata/subscribedfeeds/client/package.html5
-rw-r--r--src/com/google/wireless/gdata/subscribedfeeds/data/FeedUrl.java72
-rw-r--r--src/com/google/wireless/gdata/subscribedfeeds/data/SubscribedFeedsEntry.java53
-rw-r--r--src/com/google/wireless/gdata/subscribedfeeds/data/SubscribedFeedsFeed.java15
-rw-r--r--src/com/google/wireless/gdata/subscribedfeeds/data/package.html5
-rw-r--r--src/com/google/wireless/gdata/subscribedfeeds/package.html5
-rw-r--r--src/com/google/wireless/gdata/subscribedfeeds/parser/package.html5
-rw-r--r--src/com/google/wireless/gdata/subscribedfeeds/parser/xml/XmlSubscribedFeedsGDataParser.java75
-rw-r--r--src/com/google/wireless/gdata/subscribedfeeds/parser/xml/XmlSubscribedFeedsGDataParserFactory.java81
-rw-r--r--src/com/google/wireless/gdata/subscribedfeeds/parser/xml/package.html5
-rw-r--r--src/com/google/wireless/gdata/subscribedfeeds/serializer/package.html5
-rw-r--r--src/com/google/wireless/gdata/subscribedfeeds/serializer/xml/XmlSubscribedFeedsEntryGDataSerializer.java80
-rw-r--r--src/com/google/wireless/gdata/subscribedfeeds/serializer/xml/package.html5
110 files changed, 8240 insertions, 0 deletions
diff --git a/src/com/google/wireless/gdata/GDataException.java b/src/com/google/wireless/gdata/GDataException.java
new file mode 100644
index 0000000..cd06fc2
--- /dev/null
+++ b/src/com/google/wireless/gdata/GDataException.java
@@ -0,0 +1,64 @@
+// Copyright 2007 The Android Open Source Project
+package com.google.wireless.gdata;
+
+/**
+ * The base exception for GData operations.
+ */
+public class GDataException extends Exception {
+
+ private final Throwable cause;
+
+ /**
+ * Creates a new empty GDataException.
+ */
+ public GDataException() {
+ super();
+ cause = null;
+ }
+
+ /**
+ * Creates a new GDataException with the supplied message.
+ * @param message The message for this GDataException.
+ */
+ public GDataException(String message) {
+ super(message);
+ cause = null;
+ }
+
+ /**
+ * Creates a new GDataException with the supplied message and underlying
+ * cause.
+ *
+ * @param message The message for this GDataException.
+ * @param cause The underlying cause that was caught and wrapped by this
+ * GDataException.
+ */
+ public GDataException(String message, Throwable cause) {
+ super(message);
+ this.cause = cause;
+ }
+
+ /**
+ * Creates a new GDataException with the underlying cause.
+ *
+ * @param cause The underlying cause that was caught and wrapped by this
+ * GDataException.
+ */
+ public GDataException(Throwable cause) {
+ this("", cause);
+ }
+
+ /**
+ * @return the cause of this GDataException or null if the cause is unknown.
+ */
+ public Throwable getCause() {
+ return cause;
+ }
+
+ /**
+ * @return a string representation of this exception.
+ */
+ public String toString() {
+ return super.toString() + (cause != null ? " " + cause.toString() : "");
+ }
+}
diff --git a/src/com/google/wireless/gdata/calendar/client/CalendarClient.java b/src/com/google/wireless/gdata/calendar/client/CalendarClient.java
new file mode 100644
index 0000000..c9d4a92
--- /dev/null
+++ b/src/com/google/wireless/gdata/calendar/client/CalendarClient.java
@@ -0,0 +1,96 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.calendar.client;
+
+import com.google.wireless.gdata.calendar.data.CalendarEntry;
+import com.google.wireless.gdata.client.GDataClient;
+import com.google.wireless.gdata.client.GDataParserFactory;
+import com.google.wireless.gdata.client.GDataServiceClient;
+import com.google.wireless.gdata.client.HttpException;
+import com.google.wireless.gdata.client.QueryParams;
+import com.google.wireless.gdata.parser.GDataParser;
+import com.google.wireless.gdata.parser.ParseException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * GDataServiceClient for accessing Google Calendar. This client can access and
+ * parse both the meta feed (list of calendars for a user) and events feeds
+ * (calendar entries for a specific user). The parsers this class uses handle
+ * the XML version of feeds.
+ */
+// TODO: add a method that applies projections such as cutting the attendees.
+public class CalendarClient extends GDataServiceClient {
+ /** Service value for calendar. */
+ public static final String SERVICE = "cl";
+
+ public static final String PROJECTION_PRIVATE_FULL = "/private/full";
+ public static final String PROJECTION_PRIVATE_SELF_ATTENDANCE = "/private/full-selfattendance";
+
+ /** Standard base url for a calendar feed. */
+ private static final String CALENDAR_BASE_FEED_URL =
+ "http://www.google.com/calendar/feeds/";
+
+ /**
+ * Create a new CalendarClient. Uses the standard base URL for calendar feeds.
+ * @param client The GDataClient that should be used to authenticate
+ * requests, retrieve feeds, etc.
+ * @param factory The factory that should be used to obtain {@link GDataParser}s used by this
+ * client.
+ */
+ public CalendarClient(GDataClient client, GDataParserFactory factory) {
+ super(client, factory);
+ }
+
+ /* (non-Javadoc)
+ * @see GDataServiceClient#getServiceName
+ */
+ public String getServiceName() {
+ return SERVICE;
+ }
+
+ /**
+ * Returns the url for the default feed for a user, after applying the
+ * provided QueryParams.
+ * @param username The username for this user.
+ * @param projection the projection to use
+ * @param params The QueryParams that should be applied to the default feed.
+ * @return The url that should be used to retrieve a user's default feed.
+ */
+ public String getDefaultCalendarUrl(String username, String projection, QueryParams params) {
+ String feedUrl = CALENDAR_BASE_FEED_URL + getGDataClient().encodeUri(username);
+ feedUrl += projection;
+ if (params == null) {
+ return feedUrl;
+ }
+ return params.generateQueryUrl(feedUrl);
+ }
+
+ /**
+ * Returns the url for the metafeed for user, which contains the information about
+ * the user's calendars.
+ * @param username The username for this user.
+ * @return The url that should be used to retrieve a user's default feed.
+ */
+ public String getUserCalendarsUrl(String username) {
+ return CALENDAR_BASE_FEED_URL + getGDataClient().encodeUri(username);
+ }
+
+ /**
+ * Fetches the meta feed containing the list of calendars for a user. The
+ * caller is responsible for closing the returned {@link GDataParser}.
+ *
+ * @param feedUrl the URL of the user calendars feed
+ * @param authToken The authentication token for this user
+ * @return A GDataParser with the meta feed containing the list of
+ * calendars for this user.
+ * @throws ParseException Thrown if the feed could not be fetched.
+ */
+ public GDataParser getParserForUserCalendars(String feedUrl, String authToken)
+ throws ParseException, IOException, HttpException {
+ GDataClient gDataClient = getGDataClient();
+ InputStream is = gDataClient.getFeedAsStream(feedUrl, authToken);
+ return getGDataParserFactory().createParser(CalendarEntry.class, is);
+ }
+}
diff --git a/src/com/google/wireless/gdata/calendar/client/package.html b/src/com/google/wireless/gdata/calendar/client/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/calendar/client/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/calendar/data/CalendarEntry.java b/src/com/google/wireless/gdata/calendar/data/CalendarEntry.java
new file mode 100644
index 0000000..6e49a9f
--- /dev/null
+++ b/src/com/google/wireless/gdata/calendar/data/CalendarEntry.java
@@ -0,0 +1,161 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.calendar.data;
+
+import com.google.wireless.gdata.data.Entry;
+
+/**
+ * Entry containing information about a calendar.
+ */
+public class CalendarEntry extends Entry {
+
+ /**
+ * Access level constant indicating the user has no access to a calendar.
+ */
+ public static final byte ACCESS_NONE = 0;
+
+ /**
+ * Access level constant indicating the user can read (but not write) to
+ * a calendar.
+ */
+ public static final byte ACCESS_READ = 1;
+
+ /**
+ * Access level constant indicating the user can only view free-busy
+ * information for a calendar.
+ */
+ public static final byte ACCESS_FREEBUSY = 2;
+
+ /**
+ * Access level constant indicating the user can edit this calendar.
+ */
+ public static final byte ACCESS_EDITOR = 3;
+
+ /**
+ * Access level constant indicating the user owns this calendar.
+ */
+ public static final byte ACCESS_OWNER = 4;
+
+ private byte accessLevel = ACCESS_READ;
+ // TODO: rename to feed Url?
+ private String alternateLink = null;
+ private String color = null;
+ private boolean hidden = false;
+ private boolean selected = true;
+ private String timezone = null;
+
+ /**
+ * Creates a new, empty calendar entry.
+ */
+ public CalendarEntry() {
+ }
+
+ public void clear() {
+ super.clear();
+ accessLevel = ACCESS_READ;
+ alternateLink = null;
+ color = null;
+ hidden = false;
+ selected = true;
+ timezone = null;
+ }
+
+ /**
+ * @return the accessLevel
+ */
+ public byte getAccessLevel() {
+ return accessLevel;
+ }
+
+ /**
+ * @param accessLevel the accessLevel to set
+ */
+ public void setAccessLevel(byte accessLevel) {
+ this.accessLevel = accessLevel;
+ }
+
+ /**
+ * @return the alternateLink
+ */
+ public String getAlternateLink() {
+ return alternateLink;
+ }
+
+ /**
+ * @param alternateLink the alternateLink to set
+ */
+ public void setAlternateLink(String alternateLink) {
+ this.alternateLink = alternateLink;
+ }
+
+ /**
+ * @return the color
+ */
+ public String getColor() {
+ return color;
+ }
+
+ /**
+ * @param color the color to set
+ */
+ public void setColor(String color) {
+ this.color = color;
+ }
+
+ /**
+ * @return the hidden
+ */
+ public boolean isHidden() {
+ return hidden;
+ }
+
+ /**
+ * @param hidden the hidden to set
+ */
+ public void setHidden(boolean hidden) {
+ this.hidden = hidden;
+ }
+
+ /**
+ * @return the selected
+ */
+ public boolean isSelected() {
+ return selected;
+ }
+
+ /**
+ * @param selected the selected to set
+ */
+ public void setSelected(boolean selected) {
+ this.selected = selected;
+ }
+
+ /**
+ * @return the timezone
+ */
+ public String getTimezone() {
+ return timezone;
+ }
+
+ /**
+ * @param timezone the timezone to set
+ */
+ public void setTimezone(String timezone) {
+ this.timezone = timezone;
+ }
+
+ public void toString(StringBuffer sb) {
+ sb.append("ACCESS LEVEL: ");
+ sb.append(accessLevel);
+ sb.append('\n');
+ appendIfNotNull(sb, "ALTERNATE LINK", alternateLink);
+ appendIfNotNull(sb, "COLOR", color);
+ sb.append("HIDDEN: ");
+ sb.append(hidden);
+ sb.append('\n');
+ sb.append("SELECTED: ");
+ sb.append(selected);
+ sb.append('\n');
+ appendIfNotNull(sb, "TIMEZONE", timezone);
+ }
+}
diff --git a/src/com/google/wireless/gdata/calendar/data/CalendarsFeed.java b/src/com/google/wireless/gdata/calendar/data/CalendarsFeed.java
new file mode 100644
index 0000000..f792017
--- /dev/null
+++ b/src/com/google/wireless/gdata/calendar/data/CalendarsFeed.java
@@ -0,0 +1,18 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.calendar.data;
+
+import com.google.wireless.gdata.data.Feed;
+
+/**
+ * Meta feed containing the list of calendars for a user.
+ */
+public class CalendarsFeed extends Feed {
+
+ /**
+ * Creates a new empty calendars feed.
+ */
+ public CalendarsFeed() {
+ }
+
+}
diff --git a/src/com/google/wireless/gdata/calendar/data/EventEntry.java b/src/com/google/wireless/gdata/calendar/data/EventEntry.java
new file mode 100644
index 0000000..5f2f271
--- /dev/null
+++ b/src/com/google/wireless/gdata/calendar/data/EventEntry.java
@@ -0,0 +1,315 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.calendar.data;
+
+import com.google.wireless.gdata.data.Entry;
+
+import java.util.Hashtable;
+import java.util.Vector;
+import java.util.Enumeration;
+
+/**
+ * Entry containing information about an event in a calendar.
+ */
+public class EventEntry extends Entry {
+
+ // TODO: pack all of these enums into an int
+
+ /**
+ * Status constant indicating that a user's attendance at an event is
+ * tentative.
+ */
+ public static final byte STATUS_TENTATIVE = 0;
+
+ /**
+ * Status constant indicating that a user's attendance at an event is
+ * confirmed.
+ */
+ public static final byte STATUS_CONFIRMED = 1;
+
+ /**
+ * Status constant indicating that an event has been cancelled.
+ */
+ public static final byte STATUS_CANCELED = 2;
+
+ /**
+ * Visibility constant indicating that an event uses the user's default
+ * visibility.
+ */
+ public static final byte VISIBILITY_DEFAULT = 0;
+
+ /**
+ * Visibility constant indicating that an event has been marked
+ * confidential.
+ */
+ public static final byte VISIBILITY_CONFIDENTIAL = 1;
+
+ /**
+ * Visibility constant indicating that an event has been marked private.
+ */
+ public static final byte VISIBILITY_PRIVATE = 2;
+
+ /**
+ * Visibility constant indicating that an event has been marked public.
+ */
+ public static final byte VISIBILITY_PUBLIC = 3;
+
+ /**
+ * Transparency constant indicating that an event has been marked opaque.
+ */
+ public static final byte TRANSPARENCY_OPAQUE = 0;
+
+ /**
+ * Transparency constant indicating that an event has been marked
+ * transparent.
+ */
+ public static final byte TRANSPARENCY_TRANSPARENT = 1;
+
+ private byte status = STATUS_TENTATIVE;
+ private String recurrence = null;
+ private byte visibility = VISIBILITY_DEFAULT;
+ private byte transparency = TRANSPARENCY_OPAQUE;
+ private Vector attendees = new Vector();
+ private Vector whens = new Vector();
+ private Vector reminders = null;
+ private String originalEventId = null;
+ private String originalEventStartTime = null;
+ private String where = null;
+ private String commentsUri = null;
+ private Hashtable extendedProperties = null;
+
+ /**
+ * Creates a new empty event entry.
+ */
+ public EventEntry() {
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.wireless.gdata.data.Entry#clear()
+ */
+ public void clear() {
+ super.clear();
+ status = STATUS_TENTATIVE;
+ recurrence = null;
+ visibility = VISIBILITY_DEFAULT;
+ transparency = TRANSPARENCY_OPAQUE;
+ attendees.removeAllElements();
+ whens.removeAllElements();
+ reminders = null;
+ originalEventId = null;
+ originalEventStartTime = null;
+ where = null;
+ commentsUri = null;
+ extendedProperties = null;
+ }
+
+ /**
+ * @return the recurrence
+ */
+ public String getRecurrence() {
+ return recurrence;
+ }
+
+ /**
+ * @param recurrence the recurrence to set
+ */
+ public void setRecurrence(String recurrence) {
+ this.recurrence = recurrence;
+ }
+
+ /**
+ * @return the status
+ */
+ public byte getStatus() {
+ return status;
+ }
+
+ /**
+ * @param status the status to set
+ */
+ public void setStatus(byte status) {
+ this.status = status;
+ }
+
+ /**
+ * @return the transparency
+ */
+ public byte getTransparency() {
+ return transparency;
+ }
+
+ /**
+ * @param transparency the transparency to set
+ */
+ public void setTransparency(byte transparency) {
+ this.transparency = transparency;
+ }
+
+ /**
+ * @return the visibility
+ */
+ public byte getVisibility() {
+ return visibility;
+ }
+
+ /**
+ * @param visibility the visibility to set
+ */
+ public void setVisibility(byte visibility) {
+ this.visibility = visibility;
+ }
+
+ public void clearAttendees() {
+ attendees.clear();
+ }
+
+ public void addAttendee(Who attendee) {
+ attendees.add(attendee);
+ }
+
+ public Vector getAttendees() {
+ return attendees;
+ }
+
+ public void clearWhens() {
+ whens.clear();
+ }
+
+ public void addWhen(When when) {
+ whens.add(when);
+ }
+
+ public Vector getWhens() {
+ return whens;
+ }
+
+ public When getFirstWhen() {
+ if (whens.isEmpty()) {
+ return null;
+ }
+ return (When) whens.elementAt(0);
+ }
+
+ public Vector getReminders() {
+ return reminders;
+ }
+
+ public void addReminder(Reminder reminder) {
+ if (reminders == null) {
+ reminders = new Vector();
+ }
+ reminders.add(reminder);
+ }
+
+ public void clearReminders() {
+ reminders = null;
+ }
+
+ public String getOriginalEventId() {
+ return originalEventId;
+ }
+
+ public void setOriginalEventId(String originalEventId) {
+ this.originalEventId = originalEventId;
+ }
+
+ public String getOriginalEventStartTime() {
+ return originalEventStartTime;
+ }
+
+ public void setOriginalEventStartTime(String originalEventStartTime) {
+ this.originalEventStartTime = originalEventStartTime;
+ }
+
+ /**
+ * @return the where
+ */
+ public String getWhere() {
+ return where;
+ }
+
+ /**
+ * @param where the where to set
+ */
+ public void setWhere(String where) {
+ this.where = where;
+ }
+
+ public Hashtable getExtendedProperties() {
+ return extendedProperties;
+ }
+
+ public String getExtendedProperty(String name) {
+ if (extendedProperties == null) {
+ return null;
+ }
+ String value = null;
+ if (extendedProperties.containsKey(name)) {
+ value = (String) extendedProperties.get(name);
+ }
+ return value;
+ }
+
+ public void addExtendedProperty(String name, String value) {
+ if (extendedProperties == null) {
+ extendedProperties = new Hashtable();
+ }
+ extendedProperties.put(name, value);
+ }
+
+ public void clearExtendedProperties() {
+ extendedProperties = null;
+ }
+
+ public String getCommentsUri() {
+ return commentsUri;
+ }
+
+ public void setCommentsUri(String commentsUri) {
+ this.commentsUri = commentsUri;
+ }
+
+ public void toString(StringBuffer sb) {
+ super.toString(sb);
+ sb.append("STATUS: " + status + "\n");
+ appendIfNotNull(sb, "RECURRENCE", recurrence);
+ sb.append("VISIBILITY: " + visibility + "\n");
+ sb.append("TRANSPARENCY: " + transparency + "\n");
+
+ appendIfNotNull(sb, "ORIGINAL_EVENT_ID", originalEventId);
+ appendIfNotNull(sb, "ORIGINAL_START_TIME", originalEventStartTime);
+
+ Enumeration whos = this.attendees.elements();
+ while (whos.hasMoreElements()) {
+ Who who = (Who) whos.nextElement();
+ who.toString(sb);
+ }
+
+ Enumeration times = this.whens.elements();
+ while (times.hasMoreElements()) {
+ When when = (When) times.nextElement();
+ when.toString(sb);
+ }
+ if (reminders != null) {
+ Enumeration alarms = reminders.elements();
+ while (alarms.hasMoreElements()) {
+ Reminder reminder = (Reminder) alarms.nextElement();
+ reminder.toString(sb);
+ }
+ }
+ appendIfNotNull(sb, "WHERE", where);
+ appendIfNotNull(sb, "COMMENTS", commentsUri);
+ if (extendedProperties != null) {
+ Enumeration entryNames = extendedProperties.keys();
+ while (entryNames.hasMoreElements()) {
+ String name = (String) entryNames.nextElement();
+ String value = (String) extendedProperties.get(name);
+ sb.append(name);
+ sb.append(':');
+ sb.append(value);
+ sb.append('\n');
+ }
+ }
+ }
+}
diff --git a/src/com/google/wireless/gdata/calendar/data/EventsFeed.java b/src/com/google/wireless/gdata/calendar/data/EventsFeed.java
new file mode 100644
index 0000000..d5f093d
--- /dev/null
+++ b/src/com/google/wireless/gdata/calendar/data/EventsFeed.java
@@ -0,0 +1,33 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.calendar.data;
+
+import com.google.wireless.gdata.data.Feed;
+
+/**
+ * Feed containing events in a calendar.
+ */
+public class EventsFeed extends Feed {
+
+ private String timezone = null;
+
+ /**
+ * Creates a new empty events feed.
+ */
+ public EventsFeed() {
+ }
+
+ /**
+ * @return the timezone
+ */
+ public String getTimezone() {
+ return timezone;
+ }
+
+ /**
+ * @param timezone the timezone to set
+ */
+ public void setTimezone(String timezone) {
+ this.timezone = timezone;
+ }
+}
diff --git a/src/com/google/wireless/gdata/calendar/data/Recurrence.java b/src/com/google/wireless/gdata/calendar/data/Recurrence.java
new file mode 100644
index 0000000..bae9160
--- /dev/null
+++ b/src/com/google/wireless/gdata/calendar/data/Recurrence.java
@@ -0,0 +1,28 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.calendar.data;
+
+/**
+ * Container for information about a Recurrence.
+ */
+// TODO: get rid of this?
+public class Recurrence {
+
+ private final String recurrence;
+
+ /**
+ * Creates a new recurrence for the provide recurrence string.
+ * @param recurrence The recurrence string that should be parsed.
+ */
+ public Recurrence(String recurrence) {
+ this.recurrence = recurrence;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return recurrence;
+ }
+}
diff --git a/src/com/google/wireless/gdata/calendar/data/Reminder.java b/src/com/google/wireless/gdata/calendar/data/Reminder.java
new file mode 100644
index 0000000..5f87eb6
--- /dev/null
+++ b/src/com/google/wireless/gdata/calendar/data/Reminder.java
@@ -0,0 +1,92 @@
+// Copyright 2007 The Android Open Source Project
+package com.google.wireless.gdata.calendar.data;
+
+/**
+ * Contains information about a reminder for an event.
+ */
+public class Reminder {
+ /**
+ * Default reminder method as defined on the calendar server.
+ */
+ public static final byte METHOD_DEFAULT = 0;
+
+ /**
+ * Reminder that uses e-mail for notification.
+ */
+ public static final byte METHOD_EMAIL = 1;
+
+ /**
+ * Reminder that uses sms for notification.
+ */
+ public static final byte METHOD_SMS = 2;
+
+ /**
+ * Reminder that uses a local alert for notification.
+ */
+ public static final byte METHOD_ALERT = 3;
+
+ /**
+ * Reminder that uses a calendar-wide default time for the alarm.
+ */
+ public static final int MINUTES_DEFAULT = -1;
+
+ // do absolute times work with recurrences?
+ // private String absoluteTime;
+ private int minutes = MINUTES_DEFAULT;
+ private byte method = METHOD_DEFAULT;
+
+ /**
+ * Creates a new empty reminder.
+ */
+ public Reminder() {
+ }
+
+ /**
+ * Returns the method of the reminder.
+ * @return The method of the reminder.
+ */
+ public byte getMethod() {
+ return method;
+ }
+
+ /**
+ * Sets the method of the reminder.
+ * @param method The method of the reminder.
+ */
+ public void setMethod(byte method) {
+ this.method = method;
+ }
+
+ /**
+ * Gets how many minutes before an event that the reminder should be
+ * triggered.
+ * @return How many minutes before an event that the reminder should be
+ * triggered.
+ */
+ public int getMinutes() {
+ return minutes;
+ }
+
+ /**
+ * Sets how many minutes before an event that the reminder should be
+ * triggered.
+ * @param minutes How many minutes before an event that the reminder should
+ * be triggered.
+ */
+ public void setMinutes(int minutes) {
+ this.minutes = minutes;
+ }
+
+ public void toString(StringBuffer sb) {
+ sb.append("REMINDER MINUTES: " + minutes);
+ sb.append("\n");
+ sb.append("REMINDER METHOD: " + method);
+ sb.append("\n");
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ toString(sb);
+ return sb.toString();
+ }
+}
diff --git a/src/com/google/wireless/gdata/calendar/data/When.java b/src/com/google/wireless/gdata/calendar/data/When.java
new file mode 100644
index 0000000..4f9ab9e
--- /dev/null
+++ b/src/com/google/wireless/gdata/calendar/data/When.java
@@ -0,0 +1,53 @@
+// Copyright 2007 The Android Open Source Project
+package com.google.wireless.gdata.calendar.data;
+
+import com.google.wireless.gdata.data.StringUtils;
+
+/**
+ * Contains information about the start and end of an instance of an event.
+ */
+public class When {
+ private final String startTime;
+ private final String endTime;
+
+ /**
+ * Creates a new When.
+ * @param startTime The start of the event.
+ * @param endTime The end of the event.
+ */
+ public When(String startTime, String endTime) {
+ this.startTime = startTime;
+ this.endTime = endTime;
+ }
+
+ /**
+ * Returns the start time for the event.
+ * @return The start time for the event.
+ */
+ public String getStartTime() {
+ return startTime;
+ }
+
+ /**
+ * Returns the end time for the event.
+ * @return The end time for the event.
+ */
+ public String getEndTime() {
+ return endTime;
+ }
+
+ public void toString(StringBuffer sb) {
+ if (!StringUtils.isEmpty(startTime)) {
+ sb.append("START TIME: " + startTime + "\n");
+ }
+ if (!StringUtils.isEmpty(endTime)) {
+ sb.append("END TIME: " + endTime + "\n");
+ }
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ toString(sb);
+ return sb.toString();
+ }
+}
diff --git a/src/com/google/wireless/gdata/calendar/data/Who.java b/src/com/google/wireless/gdata/calendar/data/Who.java
new file mode 100644
index 0000000..2d438d6
--- /dev/null
+++ b/src/com/google/wireless/gdata/calendar/data/Who.java
@@ -0,0 +1,153 @@
+package com.google.wireless.gdata.calendar.data;
+
+import com.google.wireless.gdata.data.StringUtils;
+
+/**
+ * Contains information about a event attendee.
+ */
+public class Who {
+
+ /**
+ * No attendee relationhip set. Used in {@link #setRelationship}
+ * and {@link #getRelationship}.
+ */
+ public static final byte RELATIONSHIP_NONE = 0;
+
+ /**
+ * A general meeting/event attendee. Used in {@link #setRelationship}
+ * and {@link #getRelationship}.
+ */
+ public static final byte RELATIONSHIP_ATTENDEE = 1;
+
+ /**
+ * Event organizer. An organizer is not necessary an attendee.
+ * Used in {@link #setRelationship} and {@link #getRelationship}.
+ */
+ public static final byte RELATIONSHIP_ORGANIZER = 2;
+
+ /**
+ * Performer. Similar to {@link #RELATIONSHIP_SPEAKER}, but with more emphasis on art than
+ * speech delivery.
+ * Used in {@link #setRelationship} and {@link #getRelationship}.
+ */
+ public static final byte RELATIONSHIP_PERFORMER = 3;
+
+ /**
+ * Speaker. Used in {@link #setRelationship} and {@link #getRelationship}.
+ */
+ public static final byte RELATIONSHIP_SPEAKER = 4;
+
+ /**
+ * No attendee type set. Used in {@link #setType} and {@link #getType}.
+ */
+ public static final byte TYPE_NONE = 0;
+
+ /**
+ * Optional attendee. Used in {@link #setType} and {@link #getType}.
+ */
+ public static final byte TYPE_OPTIONAL = 1;
+
+ /**
+ * Required attendee. Used in {@link #setType} and {@link #getType}.
+ */
+ public static final byte TYPE_REQUIRED = 2;
+
+ /**
+ * No attendee status set. Used in {@link #setStatus} and {@link #getStatus}.
+ */
+ public static final byte STATUS_NONE = 0;
+
+
+ /**
+ * Attendee has accepted. Used in {@link #setStatus} and {@link #getStatus}.
+ */
+ public static final byte STATUS_ACCEPTED = 1;
+
+ /**
+ * Attendee has declined. Used in {@link #setStatus} and {@link #getStatus}.
+ */
+ public static final byte STATUS_DECLINED = 2;
+
+ /**
+ * Invitation has been sent, but the person has not accepted.
+ * Used in {@link #setStatus} and {@link #getStatus}.
+ */
+ public static final byte STATUS_INVITED = 3;
+
+ /**
+ * Attendee has accepted tentatively. Used in {@link #setStatus} and {@link #getStatus}.
+ */
+ public static final byte STATUS_TENTATIVE = 4;
+
+ private String email;
+ private String value;
+ private byte relationship = RELATIONSHIP_NONE;
+ private byte type = TYPE_NONE;
+ private byte status = STATUS_NONE;
+
+ /**
+ * Creates a new Who, representing event attendee information.
+ */
+ public Who() {
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public byte getRelationship() {
+ return relationship;
+ }
+
+ public void setRelationship(byte relationship) {
+ this.relationship = relationship;
+ }
+
+ public byte getType() {
+ return type;
+ }
+
+ public void setType(byte type) {
+ this.type = type;
+ }
+
+ public byte getStatus() {
+ return status;
+ }
+
+ public void setStatus(byte status) {
+ this.status = status;
+ }
+
+ protected void toString(StringBuffer sb) {
+ if (!StringUtils.isEmpty(email)) {
+ sb.append("EMAIL: " + email + "\n");
+ }
+
+ if (!StringUtils.isEmpty(value)) {
+ sb.append("VALUE: " + value + "\n");
+ }
+
+ sb.append("RELATIONSHIP: " + relationship + "\n");
+ sb.append("TYPE: " + type + "\n");
+ sb.append("STATUS: " + status + "\n");
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ toString(sb);
+ return sb.toString();
+ }
+}
diff --git a/src/com/google/wireless/gdata/calendar/data/package.html b/src/com/google/wireless/gdata/calendar/data/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/calendar/data/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/calendar/package.html b/src/com/google/wireless/gdata/calendar/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/calendar/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/calendar/parser/package.html b/src/com/google/wireless/gdata/calendar/parser/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/calendar/parser/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/calendar/parser/xml/XmlCalendarGDataParserFactory.java b/src/com/google/wireless/gdata/calendar/parser/xml/XmlCalendarGDataParserFactory.java
new file mode 100644
index 0000000..9e38ba8
--- /dev/null
+++ b/src/com/google/wireless/gdata/calendar/parser/xml/XmlCalendarGDataParserFactory.java
@@ -0,0 +1,98 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.calendar.parser.xml;
+
+import com.google.wireless.gdata.calendar.data.CalendarEntry;
+import com.google.wireless.gdata.calendar.data.EventEntry;
+import com.google.wireless.gdata.calendar.serializer.xml.XmlEventEntryGDataSerializer;
+import com.google.wireless.gdata.client.GDataParserFactory;
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.parser.GDataParser;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.parser.xml.XmlParserFactory;
+import com.google.wireless.gdata.serializer.GDataSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.InputStream;
+
+/**
+ * GDataParserFactory that creates XML GDataParsers and GDataSerializers for
+ * Google Calendar.
+ */
+public class XmlCalendarGDataParserFactory implements GDataParserFactory {
+
+ private final XmlParserFactory xmlFactory;
+
+ public XmlCalendarGDataParserFactory(XmlParserFactory xmlFactory) {
+ this.xmlFactory = xmlFactory;
+ }
+
+ /**
+ * Returns a parser for a calendars meta-feed.
+ *
+ * @param is The input stream to be parsed.
+ * @return A parser for the stream.
+ */
+ public GDataParser createCalendarsFeedParser(InputStream is)
+ throws ParseException {
+ XmlPullParser xmlParser;
+ try {
+ xmlParser = xmlFactory.createParser();
+ } catch (XmlPullParserException xppe) {
+ throw new ParseException("Could not create XmlPullParser", xppe);
+ }
+ return new XmlCalendarsGDataParser(is, xmlParser);
+ }
+
+ /*
+ * (non-javadoc)
+ *
+ * @see GDataParserFactory#createParser
+ */
+ public GDataParser createParser(InputStream is) throws ParseException {
+ XmlPullParser xmlParser;
+ try {
+ xmlParser = xmlFactory.createParser();
+ } catch (XmlPullParserException xppe) {
+ throw new ParseException("Could not create XmlPullParser", xppe);
+ }
+ return new XmlEventsGDataParser(is, xmlParser);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.wireless.gdata.client.GDataParserFactory#createParser(
+ * int, java.io.InputStream)
+ */
+ public GDataParser createParser(Class entryClass, InputStream is)
+ throws ParseException {
+ if (entryClass == CalendarEntry.class) {
+ return createCalendarsFeedParser(is);
+ } else if (entryClass == EventEntry.class) {
+ return createParser(is);
+ }
+ throw new IllegalArgumentException("Unknown entry class '" + entryClass.getName()
+ + "' specified.");
+ }
+
+ /**
+ * Creates a new {@link GDataSerializer} for the provided entry. The entry
+ * <strong>must</strong> be an instance of {@link EventEntry}.
+ *
+ * @param entry The {@link EventEntry} that should be serialized.
+ * @return The {@link GDataSerializer} that will serialize this entry.
+ * @throws IllegalArgumentException Thrown if entry is not an
+ * {@link EventEntry}.
+ * @see GDataParserFactory#createSerializer
+ */
+ public GDataSerializer createSerializer(Entry entry) {
+ if (!(entry instanceof EventEntry)) {
+ throw new IllegalArgumentException("Expected EventEntry!");
+ }
+ EventEntry eventEntry = (EventEntry) entry;
+ return new XmlEventEntryGDataSerializer(xmlFactory, eventEntry);
+ }
+}
diff --git a/src/com/google/wireless/gdata/calendar/parser/xml/XmlCalendarsGDataParser.java b/src/com/google/wireless/gdata/calendar/parser/xml/XmlCalendarsGDataParser.java
new file mode 100644
index 0000000..431fd43
--- /dev/null
+++ b/src/com/google/wireless/gdata/calendar/parser/xml/XmlCalendarsGDataParser.java
@@ -0,0 +1,136 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.calendar.parser.xml;
+
+import com.google.wireless.gdata.calendar.data.CalendarEntry;
+import com.google.wireless.gdata.calendar.data.CalendarsFeed;
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.Feed;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.parser.xml.XmlGDataParser;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * GDataParser for the meta feed listing a user's calendars.
+ */
+public class XmlCalendarsGDataParser extends XmlGDataParser {
+
+ /**
+ * Creates a new XmlCalendarsGDataParser.
+ * @param is The InputStream containing the calendars feed.
+ * @throws ParseException Thrown if an XmlPullParser could not be created.
+ */
+ public XmlCalendarsGDataParser(InputStream is, XmlPullParser parser)
+ throws ParseException {
+ super(is, parser);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.wireless.gdata.parser.xml.XmlGDataParser#createFeed()
+ */
+ protected Feed createFeed() {
+ return new CalendarsFeed();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.wireless.gdata.parser.xml.XmlGDataParser#createEntry()
+ */
+ protected Entry createEntry() {
+ return new CalendarEntry();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see XmlGDataParser#handleExtraElementInEntry
+ */
+ protected void handleExtraElementInEntry(Entry entry)
+ throws XmlPullParserException, IOException {
+
+ XmlPullParser parser = getParser();
+
+ if (!(entry instanceof CalendarEntry)) {
+ throw new IllegalArgumentException("Expected CalendarEntry!");
+ }
+ CalendarEntry calendarEntry = (CalendarEntry) entry;
+
+ // NOTE: all of these names are assumed to be in the "gcal" namespace.
+ // we do not bother checking that here.
+ String name = parser.getName();
+ if ("accesslevel".equals(name)) {
+ String accesslevelStr = parser.getAttributeValue(null /* ns */,
+ "value");
+ byte accesslevel = CalendarEntry.ACCESS_READ;
+ if ("none".equals(accesslevelStr)) {
+ accesslevel = CalendarEntry.ACCESS_NONE;
+ } else if ("read".equals(accesslevelStr)) {
+ accesslevel = CalendarEntry.ACCESS_READ;
+ } else if ("freebusy".equals(accesslevelStr)) {
+ accesslevel = CalendarEntry.ACCESS_FREEBUSY;
+ } else if ("contributor".equals(accesslevelStr)) {
+ // contributor is the access level that used to be used, but it seems to have
+ // been deprecated in favor of "editor".
+ accesslevel = CalendarEntry.ACCESS_EDITOR;
+ } else if ("editor".equals(accesslevelStr)) {
+ accesslevel = CalendarEntry.ACCESS_EDITOR;
+ } else if ("owner".equals(accesslevelStr)) {
+ accesslevel = CalendarEntry.ACCESS_OWNER;
+ }
+ calendarEntry.setAccessLevel(accesslevel);
+ } else if ("color".equals(name)) {
+ String color =
+ parser.getAttributeValue(null /* ns */, "value");
+ calendarEntry.setColor(color);
+ } else if ("hidden".equals(name)) {
+ String hiddenStr =
+ parser.getAttributeValue(null /* ns */, "value");
+ boolean hidden = false;
+ if ("false".equals(hiddenStr)) {
+ hidden = false;
+ } else if ("true".equals(hiddenStr)) {
+ hidden = true;
+ }
+ calendarEntry.setHidden(hidden);
+ // if the calendar is hidden, it cannot be selected.
+ if (hidden) {
+ calendarEntry.setSelected(false);
+ }
+ } else if ("selected".equals(name)) {
+ String selectedStr =
+ parser.getAttributeValue(null /* ns */, "value");
+ boolean selected = false;
+ if ("false".equals(selectedStr)) {
+ selected = false;
+ } else if ("true".equals(selectedStr)) {
+ selected = true;
+ }
+ calendarEntry.setSelected(selected);
+ } else if ("timezone".equals(name)) {
+ String timezone =
+ parser.getAttributeValue(null /* ns */, "value");
+ calendarEntry.setTimezone(timezone);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see XmlGDataParser#handleExtraLinkInEntry
+ */
+ protected void handleExtraLinkInEntry(String rel,
+ String type,
+ String href,
+ Entry entry)
+ throws XmlPullParserException, IOException {
+ if (("alternate".equals(rel)) &&
+ ("application/atom+xml".equals(type))) {
+ CalendarEntry calendarEntry = (CalendarEntry) entry;
+ calendarEntry.setAlternateLink(href);
+ }
+ }
+}
diff --git a/src/com/google/wireless/gdata/calendar/parser/xml/XmlEventsGDataParser.java b/src/com/google/wireless/gdata/calendar/parser/xml/XmlEventsGDataParser.java
new file mode 100644
index 0000000..7ec447e
--- /dev/null
+++ b/src/com/google/wireless/gdata/calendar/parser/xml/XmlEventsGDataParser.java
@@ -0,0 +1,426 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.calendar.parser.xml;
+
+import com.google.wireless.gdata.calendar.data.EventEntry;
+import com.google.wireless.gdata.calendar.data.EventsFeed;
+import com.google.wireless.gdata.calendar.data.When;
+import com.google.wireless.gdata.calendar.data.Reminder;
+import com.google.wireless.gdata.calendar.data.Who;
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.Feed;
+import com.google.wireless.gdata.data.StringUtils;
+import com.google.wireless.gdata.data.XmlUtils;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.parser.xml.XmlGDataParser;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * GDataParser for an events feed containing events in a calendar.
+ */
+public class XmlEventsGDataParser extends XmlGDataParser {
+
+ // whether or not we've seen reminders directly under the entry.
+ // the calendar feed sends duplicate <reminder> entries in case of
+ // recurrences, if the recurrences are expanded.
+ // if the <reminder> elements precede the <when> elements, we'll only
+ // process the <reminder> elements directly under the entry and ignore
+ // the <reminder> elements within a <when>.
+ // if the <when> elements precede the <reminder> elements, we'll first
+ // process reminders under the when, and then we'll clear them and process
+ // the reminders directly under the entry (which should take precedence).
+ // if we only see <reminder> as direct children of the entry or only see
+ // <reminder> as children of <when> elements, there is no conflict.
+ private boolean hasSeenReminder = false;
+
+ /**
+ * Creates a new XmlEventsGDataParser.
+ * @param is The InputStream that should be parsed.
+ * @throws ParseException Thrown if a parser cannot be created.
+ */
+ public XmlEventsGDataParser(InputStream is, XmlPullParser parser)
+ throws ParseException {
+ super(is, parser);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.wireless.gdata.parser.xml.XmlGDataParser#createFeed()
+ */
+ protected Feed createFeed() {
+ return new EventsFeed();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.wireless.gdata.parser.xml.XmlGDataParser#createEntry()
+ */
+ protected Entry createEntry() {
+ return new EventEntry();
+ }
+
+ @Override
+ protected void handleEntry(Entry entry) throws XmlPullParserException,
+ IOException, ParseException {
+ hasSeenReminder = false; // Reset the state for the new entry
+ super.handleEntry(entry);
+ }
+
+ protected void handleExtraElementInFeed(Feed feed)
+ throws XmlPullParserException, IOException {
+ XmlPullParser parser = getParser();
+ if (!(feed instanceof EventsFeed)) {
+ throw new IllegalArgumentException("Expected EventsFeed!");
+ }
+ EventsFeed eventsFeed = (EventsFeed) feed;
+ String name = parser.getName();
+ if ("timezone".equals(name)) {
+ String timezone = parser.getAttributeValue(null /* ns */, "value");
+ if (!StringUtils.isEmpty(timezone)) {
+ eventsFeed.setTimezone(timezone);
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see XmlGDataParser#handleExtraElementInEntry
+ */
+ protected void handleExtraElementInEntry(Entry entry)
+ throws XmlPullParserException, IOException, ParseException {
+
+ XmlPullParser parser = getParser();
+
+ if (!(entry instanceof EventEntry)) {
+ throw new IllegalArgumentException("Expected EventEntry!");
+ }
+ EventEntry eventEntry = (EventEntry) entry;
+
+ // NOTE: all of these names are assumed to be in the "gd" namespace.
+ // we do not bother checking that here.
+
+ String name = parser.getName();
+ if ("eventStatus".equals(name)) {
+ String eventStatusStr = parser.getAttributeValue(null, "value");
+ byte eventStatus = EventEntry.STATUS_TENTATIVE;
+ if ("http://schemas.google.com/g/2005#event.canceled".
+ equals(eventStatusStr)) {
+ eventStatus = EventEntry.STATUS_CANCELED;
+ } else if ("http://schemas.google.com/g/2005#event.confirmed".
+ equals(eventStatusStr)) {
+ eventStatus = EventEntry.STATUS_CONFIRMED;
+ } else if ("http://schemas.google.com/g/2005#event.tentative".
+ equals(eventStatusStr)) {
+ eventStatus = EventEntry.STATUS_TENTATIVE;
+ }
+ eventEntry.setStatus(eventStatus);
+ } else if ("recurrence".equals(name)) {
+ String recurrence = XmlUtils.extractChildText(parser);
+ eventEntry.setRecurrence(recurrence);
+ } else if ("transparency".equals(name)) {
+ String transparencyStr = parser.getAttributeValue(null, "value");
+ byte transparency = EventEntry.TRANSPARENCY_OPAQUE;
+ if ("http://schemas.google.com/g/2005#event.opaque".
+ equals(transparencyStr)) {
+ transparency = EventEntry.TRANSPARENCY_OPAQUE;
+ } else if ("http://schemas.google.com/g/2005#event.transparent".
+ equals(transparencyStr)) {
+ transparency = EventEntry.TRANSPARENCY_TRANSPARENT;
+ }
+ eventEntry.setTransparency(transparency);
+ } else if ("visibility".equals(name)) {
+ String visibilityStr = parser.getAttributeValue(null, "value");
+ byte visibility = EventEntry.VISIBILITY_DEFAULT;
+ if ("http://schemas.google.com/g/2005#event.confidential".
+ equals(visibilityStr)) {
+ visibility = EventEntry.VISIBILITY_CONFIDENTIAL;
+ } else if ("http://schemas.google.com/g/2005#event.default"
+ .equals(visibilityStr)) {
+ visibility = EventEntry.VISIBILITY_DEFAULT;
+ } else if ("http://schemas.google.com/g/2005#event.private"
+ .equals(visibilityStr)) {
+ visibility = EventEntry.VISIBILITY_PRIVATE;
+ } else if ("http://schemas.google.com/g/2005#event.public"
+ .equals(visibilityStr)) {
+ visibility = EventEntry.VISIBILITY_PUBLIC;
+ }
+ eventEntry.setVisibility(visibility);
+ } else if ("who".equals(name)) {
+ handleWho(eventEntry);
+ } else if ("when".equals(name)) {
+ handleWhen(eventEntry);
+ } else if ("reminder".equals(name)) {
+ if (!hasSeenReminder) {
+ // if this is the first <reminder> we've seen directly under the
+ // entry, clear any previously seen reminders (under <when>s)
+ eventEntry.clearReminders();
+ hasSeenReminder = true;
+ }
+ handleReminder(eventEntry);
+ } else if ("originalEvent".equals(name)) {
+ handleOriginalEvent(eventEntry);
+ } else if ("where".equals(name)) {
+ String where = parser.getAttributeValue(null /* ns */,
+ "valueString");
+ String rel = parser.getAttributeValue(null /* ns */,
+ "rel");
+ if (StringUtils.isEmpty(rel) ||
+ "http://schemas.google.com/g/2005#event".equals(rel)) {
+ eventEntry.setWhere(where);
+ }
+ // TODO: handle entryLink?
+ } else if ("feedLink".equals(name)) {
+ // TODO: check that the parent is a gd:comments
+ String commentsUri = parser.getAttributeValue(null /* ns */, "href");
+ eventEntry.setCommentsUri(commentsUri);
+ } else if ("extendedProperty".equals(name)) {
+ String propertyName = parser.getAttributeValue(null /* ns */, "name");
+ String propertyValue = parser.getAttributeValue(null /* ns */, "value");
+ eventEntry.addExtendedProperty(propertyName, propertyValue);
+ }
+ }
+
+ private void handleWho(EventEntry eventEntry)
+ throws XmlPullParserException, IOException, ParseException {
+
+ XmlPullParser parser = getParser();
+
+ int eventType = parser.getEventType();
+ String name = parser.getName();
+
+ if (eventType != XmlPullParser.START_TAG ||
+ (!"who".equals(parser.getName()))) {
+ // should not happen.
+ throw new
+ IllegalStateException("Expected <who>: Actual "
+ + "element: <"
+ + name + ">");
+ }
+
+ String email =
+ parser.getAttributeValue(null /* ns */, "email");
+ String relString =
+ parser.getAttributeValue(null /* ns */, "rel");
+ String value =
+ parser.getAttributeValue(null /* ns */, "valueString");
+
+ Who who = new Who();
+ who.setEmail(email);
+ who.setValue(value);
+ byte rel = Who.RELATIONSHIP_NONE;
+ if ("http://schemas.google.com/g/2005#event.attendee".equals(relString)) {
+ rel = Who.RELATIONSHIP_ATTENDEE;
+ } else if ("http://schemas.google.com/g/2005#event.organizer".equals(relString)) {
+ rel = Who.RELATIONSHIP_ORGANIZER;
+ } else if ("http://schemas.google.com/g/2005#event.performer".equals(relString)) {
+ rel = Who.RELATIONSHIP_PERFORMER;
+ } else if ("http://schemas.google.com/g/2005#event.speaker".equals(relString)) {
+ rel = Who.RELATIONSHIP_SPEAKER;
+ } else if (StringUtils.isEmpty(relString)) {
+ rel = Who.RELATIONSHIP_ATTENDEE;
+ } else {
+ throw new ParseException("Unexpected rel: " + relString);
+ }
+ who.setRelationship(rel);
+
+ eventEntry.addAttendee(who);
+
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ switch (eventType) {
+ case XmlPullParser.START_TAG:
+ name = parser.getName();
+ if ("attendeeStatus".equals(name)) {
+ String statusString =
+ parser.getAttributeValue(null /* ns */, "value");
+ byte status = Who.STATUS_NONE;
+ if ("http://schemas.google.com/g/2005#event.accepted".
+ equals(statusString)) {
+ status = Who.STATUS_ACCEPTED;
+ } else if ("http://schemas.google.com/g/2005#event.declined".
+ equals(statusString)) {
+ status = Who.STATUS_DECLINED;
+ } else if ("http://schemas.google.com/g/2005#event.invited".
+ equals(statusString)) {
+ status = Who.STATUS_INVITED;
+ } else if ("http://schemas.google.com/g/2005#event.tentative".
+ equals(statusString)) {
+ status = Who.STATUS_TENTATIVE;
+ } else if (StringUtils.isEmpty(statusString)) {
+ status = Who.STATUS_TENTATIVE;
+ } else {
+ throw new ParseException("Unexpected status: " + statusString);
+ }
+ who.setStatus(status);
+ } else if ("attendeeType".equals(name)) {
+ String typeString= XmlUtils.extractChildText(parser);
+ byte type = Who.TYPE_NONE;
+ if ("http://schemas.google.com/g/2005#event.optional".equals(typeString)) {
+ type = Who.TYPE_OPTIONAL;
+ } else if ("http://schemas.google.com/g/2005#event.required".
+ equals(typeString)) {
+ type = Who.TYPE_REQUIRED;
+ } else if (StringUtils.isEmpty(typeString)) {
+ type = Who.TYPE_REQUIRED;
+ } else {
+ throw new ParseException("Unexpected type: " + typeString);
+ }
+ who.setType(type);
+ }
+ break;
+ case XmlPullParser.END_TAG:
+ name = parser.getName();
+ if ("who".equals(name)) {
+ return;
+ }
+ default:
+ // ignore
+ }
+
+ eventType = parser.next();
+ }
+ }
+
+ private void handleWhen(EventEntry eventEntry)
+ throws XmlPullParserException, IOException {
+
+ XmlPullParser parser = getParser();
+
+ int eventType = parser.getEventType();
+ String name = parser.getName();
+
+ if (eventType != XmlPullParser.START_TAG ||
+ (!"when".equals(parser.getName()))) {
+ // should not happen.
+ throw new
+ IllegalStateException("Expected <when>: Actual "
+ + "element: <"
+ + name + ">");
+ }
+
+ String startTime =
+ parser.getAttributeValue(null /* ns */, "startTime");
+ String endTime =
+ parser.getAttributeValue(null /* ns */, "endTime");
+
+ When when = new When(startTime, endTime);
+ eventEntry.addWhen(when);
+ boolean firstWhen = eventEntry.getWhens().size() == 1;
+ // we only parse reminders under the when if reminders have not already
+ // been handled (directly under the entry, or in a previous when for
+ // this entry)
+ boolean handleReminders = firstWhen && !hasSeenReminder;
+
+ eventType = parser.next();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ switch (eventType) {
+ case XmlPullParser.START_TAG:
+ name = parser.getName();
+ if ("reminder".equals(name)) {
+ // only want to store reminders on the first when. they
+ // should have the same values for all other instances.
+ if (handleReminders) {
+ handleReminder(eventEntry);
+ }
+ }
+ break;
+ case XmlPullParser.END_TAG:
+ name = parser.getName();
+ if ("when".equals(name)) {
+ return;
+ }
+ default:
+ // ignore
+ }
+
+ eventType = parser.next();
+ }
+ }
+
+ private void handleReminder(EventEntry eventEntry) {
+ XmlPullParser parser = getParser();
+
+ Reminder reminder = new Reminder();
+ eventEntry.addReminder(reminder);
+
+ String methodStr = parser.getAttributeValue(null /* ns */,
+ "method");
+ String minutesStr = parser.getAttributeValue(null /* ns */,
+ "minutes");
+ String hoursStr = parser.getAttributeValue(null /* ns */,
+ "hours");
+ String daysStr = parser.getAttributeValue(null /* ns */,
+ "days");
+
+ if (!StringUtils.isEmpty(methodStr)) {
+ if ("alert".equals(methodStr)) {
+ reminder.setMethod(Reminder.METHOD_ALERT);
+ } else if ("email".equals(methodStr)) {
+ reminder.setMethod(Reminder.METHOD_EMAIL);
+ } else if ("sms".equals(methodStr)) {
+ reminder.setMethod(Reminder.METHOD_SMS);
+ }
+ }
+
+ int minutes = Reminder.MINUTES_DEFAULT;
+ if (!StringUtils.isEmpty(minutesStr)) {
+ minutes = StringUtils.parseInt(minutesStr, minutes);
+ } else if (!StringUtils.isEmpty(hoursStr)) {
+ minutes = 60*StringUtils.parseInt(hoursStr, minutes);
+ } else if (!StringUtils.isEmpty(daysStr)) {
+ minutes = 24*60*StringUtils.parseInt(daysStr, minutes);
+ }
+ // TODO: support absolute times?
+ if (minutes < 0) {
+ minutes = Reminder.MINUTES_DEFAULT;
+ }
+ reminder.setMinutes(minutes);
+ }
+
+ private void handleOriginalEvent(EventEntry eventEntry)
+ throws XmlPullParserException, IOException {
+
+ XmlPullParser parser = getParser();
+
+ int eventType = parser.getEventType();
+ String name = parser.getName();
+
+ if (eventType != XmlPullParser.START_TAG ||
+ (!"originalEvent".equals(parser.getName()))) {
+ // should not happen.
+ throw new
+ IllegalStateException("Expected <originalEvent>: Actual "
+ + "element: <"
+ + name + ">");
+ }
+
+ eventEntry.setOriginalEventId(
+ parser.getAttributeValue(null /* ns */, "href"));
+
+ eventType = parser.next();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ switch (eventType) {
+ case XmlPullParser.START_TAG:
+ name = parser.getName();
+ if ("when".equals(name)) {
+ eventEntry.setOriginalEventStartTime(
+ parser.getAttributeValue(null/*ns*/, "startTime"));
+ }
+ break;
+ case XmlPullParser.END_TAG:
+ name = parser.getName();
+ if ("originalEvent".equals(name)) {
+ return;
+ }
+ default:
+ // ignore
+ }
+
+ eventType = parser.next();
+ }
+ }
+}
diff --git a/src/com/google/wireless/gdata/calendar/parser/xml/package.html b/src/com/google/wireless/gdata/calendar/parser/xml/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/calendar/parser/xml/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/calendar/serializer/package.html b/src/com/google/wireless/gdata/calendar/serializer/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/calendar/serializer/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/calendar/serializer/xml/XmlEventEntryGDataSerializer.java b/src/com/google/wireless/gdata/calendar/serializer/xml/XmlEventEntryGDataSerializer.java
new file mode 100644
index 0000000..ccb701f
--- /dev/null
+++ b/src/com/google/wireless/gdata/calendar/serializer/xml/XmlEventEntryGDataSerializer.java
@@ -0,0 +1,399 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.calendar.serializer.xml;
+
+import com.google.wireless.gdata.calendar.data.EventEntry;
+import com.google.wireless.gdata.calendar.data.When;
+import com.google.wireless.gdata.calendar.data.Reminder;
+import com.google.wireless.gdata.calendar.data.Who;
+import com.google.wireless.gdata.data.StringUtils;
+import com.google.wireless.gdata.parser.xml.XmlGDataParser;
+import com.google.wireless.gdata.parser.xml.XmlParserFactory;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.serializer.xml.XmlEntryGDataSerializer;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+
+/**
+ * Serializes Google Calendar event entries into the Atom XML format.
+ */
+// TODO: move all strings into constants. share with parser?
+public class XmlEventEntryGDataSerializer extends XmlEntryGDataSerializer {
+
+ public static final String NAMESPACE_GCAL = "gCal";
+ public static final String NAMESPACE_GCAL_URI =
+ "http://schemas.google.com/gCal/2005";
+
+ public XmlEventEntryGDataSerializer(XmlParserFactory factory,
+ EventEntry entry) {
+ super(factory, entry);
+ }
+
+ protected EventEntry getEventEntry() {
+ return (EventEntry) getEntry();
+ }
+
+ protected void declareExtraEntryNamespaces(XmlSerializer serializer)
+ throws IOException {
+ serializer.setPrefix(NAMESPACE_GCAL, NAMESPACE_GCAL_URI);
+ }
+
+ /* (non-Javadoc)
+ * @see XmlEntryGDataSerializer#serializeExtraEntryContents
+ */
+ protected void serializeExtraEntryContents(XmlSerializer serializer,
+ int format)
+ throws IOException, ParseException {
+ EventEntry entry = getEventEntry();
+
+ serializeEventStatus(serializer, entry.getStatus());
+ serializeTransparency(serializer, entry.getTransparency());
+ serializeVisibility(serializer, entry.getVisibility());
+ Enumeration attendees = entry.getAttendees().elements();
+ while (attendees.hasMoreElements()) {
+ Who attendee = (Who) attendees.nextElement();
+ serializeWho(serializer, entry, attendee);
+ }
+
+ serializeRecurrence(serializer, entry.getRecurrence());
+ // either serialize reminders directly under the entry, or serialize
+ // whens (with reminders within the whens) -- should be just one.
+ if (entry.getRecurrence() != null) {
+ if (entry.getReminders() != null) {
+ Enumeration reminders = entry.getReminders().elements();
+ while (reminders.hasMoreElements()) {
+ Reminder reminder = (Reminder) reminders.nextElement();
+ serializeReminder(serializer, reminder);
+ }
+ }
+ } else {
+ Enumeration whens = entry.getWhens().elements();
+ while (whens.hasMoreElements()) {
+ When when = (When) whens.nextElement();
+ serializeWhen(serializer, entry, when);
+ }
+ }
+ serializeOriginalEvent(serializer,
+ entry.getOriginalEventId(),
+ entry.getOriginalEventStartTime());
+ serializeWhere(serializer, entry.getWhere());
+
+ serializeCommentsUri(serializer, entry.getCommentsUri());
+
+ Hashtable extendedProperties = entry.getExtendedProperties();
+ if (extendedProperties != null) {
+ Enumeration propertyNames = extendedProperties.keys();
+ while (propertyNames.hasMoreElements()) {
+ String propertyName = (String) propertyNames.nextElement();
+ String propertyValue = (String) extendedProperties.get(propertyName);
+ serializeExtendedProperty(serializer, propertyName, propertyValue);
+ }
+ }
+ }
+
+ private static void serializeEventStatus(XmlSerializer serializer,
+ byte status)
+ throws IOException {
+
+ String statusString;
+
+ switch (status) {
+ case EventEntry.STATUS_TENTATIVE:
+ statusString = "http://schemas.google.com/g/2005#event.tentative";
+ break;
+ case EventEntry.STATUS_CANCELED:
+ statusString = "http://schemas.google.com/g/2005#event.canceled";
+ break;
+ case EventEntry.STATUS_CONFIRMED:
+ statusString = "http://schemas.google.com/g/2005#event.confirmed";
+ break;
+ default:
+ // should not happen
+ // TODO: log this
+ statusString = "http://schemas.google.com/g/2005#event.tentative";
+ }
+
+ serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "eventStatus");
+ serializer.attribute(null /* ns */, "value", statusString);
+ serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "eventStatus");
+ }
+
+ private static void serializeRecurrence(XmlSerializer serializer,
+ String recurrence)
+ throws IOException {
+ if (StringUtils.isEmpty(recurrence)) {
+ return;
+ }
+ serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "recurrence");
+ serializer.text(recurrence);
+ serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "recurrence");
+ }
+
+ private static void serializeTransparency(XmlSerializer serializer,
+ byte transparency)
+ throws IOException {
+
+ String transparencyString;
+
+ switch (transparency) {
+ case EventEntry.TRANSPARENCY_OPAQUE:
+ transparencyString =
+ "http://schemas.google.com/g/2005#event.opaque";
+ break;
+ case EventEntry.TRANSPARENCY_TRANSPARENT:
+ transparencyString =
+ "http://schemas.google.com/g/2005#event.transparent";
+ break;
+ default:
+ // should not happen
+ // TODO: log this
+ transparencyString =
+ "http://schemas.google.com/g/2005#event.transparent";
+ }
+
+ serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "transparency");
+ serializer.attribute(null /* ns */, "value", transparencyString);
+ serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "transparency");
+ }
+
+
+ private static void serializeVisibility(XmlSerializer serializer,
+ byte visibility)
+ throws IOException {
+
+ String visibilityString;
+
+ switch (visibility) {
+ case EventEntry.VISIBILITY_DEFAULT:
+ visibilityString = "http://schemas.google.com/g/2005#event.default";
+ break;
+ case EventEntry.VISIBILITY_CONFIDENTIAL:
+ visibilityString =
+ "http://schemas.google.com/g/2005#event.confidential";
+ break;
+ case EventEntry.VISIBILITY_PRIVATE:
+ visibilityString = "http://schemas.google.com/g/2005#event.private";
+ break;
+ case EventEntry.VISIBILITY_PUBLIC:
+ visibilityString = "http://schemas.google.com/g/2005#event.public";
+ break;
+ default:
+ // should not happen
+ // TODO: log this
+ visibilityString = "http://schemas.google.com/g/2005#event.default";
+ }
+
+ serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "visibility");
+ serializer.attribute(null /* ns */, "value", visibilityString);
+ serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "visibility");
+ }
+
+ private static void serializeWho(XmlSerializer serializer,
+ EventEntry entry,
+ Who who)
+ throws IOException, ParseException {
+ serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "who");
+ String email = who.getEmail();
+ if (!StringUtils.isEmpty(email)) {
+ serializer.attribute(null /* ns */, "email", email);
+ }
+
+ String value = who.getValue();
+ if (!StringUtils.isEmpty(value)) {
+ serializer.attribute(null /* ns */, "valueString", value);
+ }
+
+ String rel = null;
+ switch (who.getRelationship()) {
+ case Who.RELATIONSHIP_NONE:
+ break;
+ case Who.RELATIONSHIP_ATTENDEE:
+ rel = "http://schemas.google.com/g/2005#event.attendee";
+ break;
+ case Who.RELATIONSHIP_ORGANIZER:
+ rel = "http://schemas.google.com/g/2005#event.organizer";
+ break;
+ case Who.RELATIONSHIP_PERFORMER:
+ rel = "http://schemas.google.com/g/2005#event.performer";
+ break;
+ case Who.RELATIONSHIP_SPEAKER:
+ rel = "http://schemas.google.com/g/2005#event.speaker";
+ break;
+ default:
+ throw new ParseException("Unexpected rel: " + who.getRelationship());
+ }
+ if (!StringUtils.isEmpty(rel)) {
+ serializer.attribute(null /* ns */, "rel", rel);
+ }
+
+ String status = null;
+ switch (who.getStatus()) {
+ case Who.STATUS_NONE:
+ break;
+ case Who.STATUS_ACCEPTED:
+ status = "http://schemas.google.com/g/2005#event.accepted";
+ break;
+ case Who.STATUS_DECLINED:
+ status = "http://schemas.google.com/g/2005#event.declined";
+ break;
+ case Who.STATUS_INVITED:
+ status = "http://schemas.google.com/g/2005#event.invited";
+ break;
+ case Who.STATUS_TENTATIVE:
+ status = "http://schemas.google.com/g/2005#event.tentative";
+ break;
+ default:
+ throw new ParseException("Unexpected status: " + who.getStatus());
+ }
+ if (!StringUtils.isEmpty(status)) {
+ serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI,
+ "attendeeStatus");
+ serializer.attribute(null /* ns */, "value", status);
+ serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI,
+ "attendeeStatus");
+ }
+
+ String type = null;
+ switch (who.getType()) {
+ case Who.TYPE_NONE:
+ break;
+ case Who.TYPE_REQUIRED:
+ type = "http://schemas.google.com/g/2005#event.required";
+ break;
+ case Who.TYPE_OPTIONAL:
+ type = "http://schemas.google.com/g/2005#event.optional";
+ break;
+ default:
+ throw new ParseException("Unexpected type: " + who.getType());
+ }
+ if (!StringUtils.isEmpty(type)) {
+ serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI,
+ "attendeeType");
+ serializer.attribute(null /* ns */, "value", type);
+ serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "attendeeType");
+ }
+ serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "who");
+ }
+
+ private static void serializeWhen(XmlSerializer serializer,
+ EventEntry entry,
+ When when)
+ throws IOException {
+ // TODO: throw exn if startTime is empty but endTime is not?
+ String startTime = when.getStartTime();
+ String endTime = when.getEndTime();
+ if (StringUtils.isEmpty(when.getStartTime())) {
+ return;
+ }
+
+ serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "when");
+ serializer.attribute(null /* ns */, "startTime", startTime);
+ if (!StringUtils.isEmpty(endTime)) {
+ serializer.attribute(null /* ns */, "endTime", endTime);
+ }
+ if (entry.getReminders() != null) {
+ Enumeration reminders = entry.getReminders().elements();
+ while (reminders.hasMoreElements()) {
+ Reminder reminder = (Reminder) reminders.nextElement();
+ serializeReminder(serializer, reminder);
+ }
+ }
+ serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "when");
+ }
+
+ private static void serializeReminder(XmlSerializer serializer,
+ Reminder reminder)
+ throws IOException {
+
+ serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "reminder");
+ byte method = reminder.getMethod();
+ String methodStr = null;
+ switch (method) {
+ case Reminder.METHOD_ALERT:
+ methodStr = "alert";
+ break;
+ case Reminder.METHOD_EMAIL:
+ methodStr = "email";
+ break;
+ case Reminder.METHOD_SMS:
+ methodStr = "sms";
+ break;
+ }
+ if (methodStr != null) {
+ serializer.attribute(null /* ns */, "method", methodStr);
+ }
+
+ int minutes = reminder.getMinutes();
+ if (minutes != Reminder.MINUTES_DEFAULT) {
+ serializer.attribute(null /* ns */, "minutes",
+ Integer.toString(minutes));
+ }
+
+ serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "reminder");
+ }
+
+ private static void serializeOriginalEvent(XmlSerializer serializer,
+ String originalEventId,
+ String originalEventTime)
+ throws IOException {
+ if (StringUtils.isEmpty(originalEventId) ||
+ StringUtils.isEmpty(originalEventTime)) {
+ return;
+ }
+
+
+ serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "originalEvent");
+ int index = originalEventId.lastIndexOf("/");
+ if (index != -1) {
+ String id = originalEventId.substring(index + 1);
+ if (!StringUtils.isEmpty(id)) {
+ serializer.attribute(null /* ns */, "id", id);
+ }
+ }
+ serializer.attribute(null /* ns */, "href", originalEventId);
+ serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "when");
+ serializer.attribute(null /* ns */, "startTime", originalEventTime);
+ serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "when");
+ serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "originalEvent");
+ }
+
+
+ private static void serializeWhere(XmlSerializer serializer,
+ String where)
+ throws IOException {
+ if (StringUtils.isEmpty(where)) {
+ return;
+ }
+
+ serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "where");
+ serializer.attribute(null /* ns */, "valueString", where);
+ serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "where");
+ }
+
+ private static void serializeCommentsUri(XmlSerializer serializer,
+ String commentsUri)
+ throws IOException {
+ if (commentsUri == null) {
+ return;
+ }
+
+ serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "feedLink");
+ serializer.attribute(null /* ns */, "href", commentsUri);
+ serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "feedLink");
+ }
+
+ private static void serializeExtendedProperty(XmlSerializer serializer,
+ String name,
+ String value)
+ throws IOException {
+ serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "extendedProperty");
+ serializer.attribute(null /* ns */, "name", name);
+ serializer.attribute(null /* ns */, "value", value);
+ serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "extendedProperty");
+ }
+}
diff --git a/src/com/google/wireless/gdata/calendar/serializer/xml/package.html b/src/com/google/wireless/gdata/calendar/serializer/xml/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/calendar/serializer/xml/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/client/GDataClient.java b/src/com/google/wireless/gdata/client/GDataClient.java
new file mode 100644
index 0000000..4162fae
--- /dev/null
+++ b/src/com/google/wireless/gdata/client/GDataClient.java
@@ -0,0 +1,149 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.client;
+
+import com.google.wireless.gdata.serializer.GDataSerializer;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Interface for interacting with a GData server. Specific platforms can
+ * provide their own implementations using the available networking and HTTP
+ * stack for that platform.
+ */
+public interface GDataClient {
+
+ /**
+ * Closes this GDataClient, cleaning up any resources, persistent connections, etc.,
+ * it may have.
+ */
+ void close();
+
+ /**
+ * URI encodes the supplied uri (using UTF-8).
+ * @param uri The uri that should be encoded.
+ * @return The encoded URI.
+ */
+ // TODO: get rid of this, if we write our own URI encoding library.
+ String encodeUri(String uri);
+
+ /**
+ * Creates a new QueryParams that should be used to restrict the feed
+ * contents that are fetched.
+ * @return A new QueryParams.
+ */
+ // TODO: get rid of this, if we write a generic QueryParams that can encode
+ // querystring params/values.
+ QueryParams createQueryParams();
+
+ /**
+ * Connects to a GData server (specified by the feedUrl) and fetches the
+ * specified feed as an InputStream. The caller is responsible for calling
+ * {@link InputStream#close()} on the returned {@link InputStream}.
+ *
+ * @param feedUrl The feed that should be fetched.
+ * @param authToken The authentication token that should be used when
+ * fetching the feed.
+ * @return An InputStream for the feed.
+ * @throws IOException Thrown if an io error occurs while communicating with
+ * the service.
+ * @throws HttpException if the service returns an error response.
+ */
+ InputStream getFeedAsStream(String feedUrl,
+ String authToken)
+ throws HttpException, IOException;
+
+ /**
+ * Connects to a GData server (specified by the mediaEntryUrl) and fetches the
+ * specified media entry as an InputStream. The caller is responsible for calling
+ * {@link InputStream#close()} on the returned {@link InputStream}.
+ *
+ * @param mediaEntryUrl The media entry that should be fetched.
+ * @param authToken The authentication token that should be used when
+ * fetching the media entry.
+ * @return An InputStream for the media entry.
+ * @throws IOException Thrown if an io error occurs while communicating with
+ * the service.
+ * @throws HttpException if the service returns an error response.
+ */
+ InputStream getMediaEntryAsStream(String mediaEntryUrl, String authToken)
+ throws HttpException, IOException;
+
+ // TODO: support batch update
+
+ /**
+ * Connects to a GData server (specified by the feedUrl) and creates a new
+ * entry. The response from the server is returned as an
+ * {@link InputStream}. The caller is responsible for calling
+ * {@link InputStream#close()} on the returned {@link InputStream}.
+ *
+ * @param feedUrl The feed url where the entry should be created.
+ * @param authToken The authentication token that should be used when
+ * creating the entry.
+ * @param entry The entry that should be created.
+ * @throws IOException Thrown if an io error occurs while communicating with
+ * the service.
+ * @throws HttpException if the service returns an error response.
+ */
+ InputStream createEntry(String feedUrl,
+ String authToken,
+ GDataSerializer entry)
+ throws HttpException, IOException;
+
+ /**
+ * Connects to a GData server (specified by the editUri) and updates an
+ * existing entry. The response from the server is returned as an
+ * {@link InputStream}. The caller is responsible for calling
+ * {@link InputStream#close()} on the returned {@link InputStream}.
+ *
+ * @param editUri The edit uri that should be used for updating the entry.
+ * @param authToken The authentication token that should be used when
+ * updating the entry.
+ * @param entry The entry that should be updated.
+ * @throws IOException Thrown if an io error occurs while communicating with
+ * the service.
+ * @throws HttpException if the service returns an error response.
+ */
+ InputStream updateEntry(String editUri,
+ String authToken,
+ GDataSerializer entry)
+ throws HttpException, IOException;
+
+ /**
+ * Connects to a GData server (specified by the editUri) and deletes an
+ * existing entry.
+ *
+ * @param editUri The edit uri that should be used for deleting the entry.
+ * @param authToken The authentication token that should be used when
+ * deleting the entry.
+ * @throws IOException Thrown if an io error occurs while communicating with
+ * the service.
+ * @throws HttpException if the service returns an error response.
+ */
+ void deleteEntry(String editUri,
+ String authToken)
+ throws HttpException, IOException;
+
+ /**
+ * Connects to a GData server (specified by the editUri) and updates an
+ * existing media entry. The response from the server is returned as an
+ * {@link InputStream}. The caller is responsible for calling
+ * {@link InputStream#close()} on the returned {@link InputStream}.
+ *
+ * @param editUri The edit uri that should be used for updating the entry.
+ * @param authToken The authentication token that should be used when
+ * updating the entry.
+ * @param mediaEntryInputStream The {@link InputStream} that contains the new
+ * value of the resource
+ * @param contentType The contentType of the new media entry
+ * @throws IOException Thrown if an io error occurs while communicating with
+ * the service.
+ * @throws HttpException if the service returns an error response.
+ * @return The {@link InputStream} that contains the metadata associated with the
+ * new version of the media entry.
+ */
+ public InputStream updateMediaEntry(String editUri, String authToken,
+ InputStream mediaEntryInputStream, String contentType)
+ throws HttpException, IOException;
+}
diff --git a/src/com/google/wireless/gdata/client/GDataParserFactory.java b/src/com/google/wireless/gdata/client/GDataParserFactory.java
new file mode 100644
index 0000000..37a2899
--- /dev/null
+++ b/src/com/google/wireless/gdata/client/GDataParserFactory.java
@@ -0,0 +1,48 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.client;
+
+import com.google.wireless.gdata.parser.GDataParser;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.serializer.GDataSerializer;
+
+import java.io.InputStream;
+
+/**
+ * Factory that creates {@link GDataParser}s and {@link GDataSerializer}s.
+ */
+public interface GDataParserFactory {
+ /**
+ * Creates a new {@link GDataParser} for the provided InputStream.
+ *
+ * @param entryClass Specify the class of Entry objects that are to be parsed. This
+ * lets createParser know which parser to create.
+ * @param is The InputStream that should be parsed. @return The GDataParser that will parse is.
+ * @throws ParseException Thrown if the GDataParser could not be created.
+ * @throws IllegalArgumentException if the feed type is unknown.
+ */
+ GDataParser createParser(Class entryClass, InputStream is)
+ throws ParseException;
+
+ /**
+ * Creates a new {@link GDataParser} for the provided InputStream, using the
+ * default feed type for the client.
+ *
+ * @param is The InputStream that should be parsed.
+ * @return The GDataParser that will parse is.
+ * @throws ParseException Thrown if the GDataParser could not be created.
+ * Note that this can occur if the feed in the InputStream is not of
+ * the default type assumed by this method.
+ * @see #createParser(Class,InputStream)
+ */
+ GDataParser createParser(InputStream is) throws ParseException;
+
+ /**
+ * Creates a new {@link GDataSerializer} for the provided Entry.
+ *
+ * @param entry The Entry that should be serialized.
+ * @return The GDataSerializer that will serialize entry.
+ */
+ GDataSerializer createSerializer(Entry entry);
+}
diff --git a/src/com/google/wireless/gdata/client/GDataServiceClient.java b/src/com/google/wireless/gdata/client/GDataServiceClient.java
new file mode 100644
index 0000000..4eae5b1
--- /dev/null
+++ b/src/com/google/wireless/gdata/client/GDataServiceClient.java
@@ -0,0 +1,209 @@
+// Copyright 2008 The Android Open Source Project
+
+package com.google.wireless.gdata.client;
+
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.MediaEntry;
+import com.google.wireless.gdata.data.StringUtils;
+import com.google.wireless.gdata.parser.GDataParser;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.serializer.GDataSerializer;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Abstract base class for service-specific clients to access GData feeds.
+ */
+public abstract class GDataServiceClient {
+ private final GDataClient gDataClient;
+ private final GDataParserFactory gDataParserFactory;
+
+ public GDataServiceClient(GDataClient gDataClient,
+ GDataParserFactory gDataParserFactory) {
+ this.gDataClient = gDataClient;
+ this.gDataParserFactory = gDataParserFactory;
+ }
+
+ /**
+ * Returns the {@link GDataClient} being used by this GDataServiceClient.
+ * @return The {@link GDataClient} being used by this GDataServiceClient.
+ */
+ protected GDataClient getGDataClient() {
+ return gDataClient;
+ }
+
+ /**
+ * Returns the {@link GDataParserFactory} being used by this
+ * GDataServiceClient.
+ * @return The {@link GDataParserFactory} being used by this
+ * GDataServiceClient.
+ */
+ protected GDataParserFactory getGDataParserFactory() {
+ return gDataParserFactory;
+ }
+
+ /**
+ * Returns the name of the service. Used for authentication.
+ * @return The name of the service.
+ */
+ public abstract String getServiceName();
+
+ /**
+ * Creates {@link QueryParams} that can be used to restrict the feed
+ * contents that are fetched.
+ * @return The QueryParams that can be used with this client.
+ */
+ public QueryParams createQueryParams() {
+ return gDataClient.createQueryParams();
+ }
+
+ /**
+ * Fetches a feed for this user. The caller is responsible for closing the
+ * returned {@link GDataParser}.
+ *
+ * @param feedEntryClass the class of Entry that is contained in the feed
+ * @param feedUrl ThAe URL of the feed that should be fetched.
+ * @param authToken The authentication token for this user.
+ * @return A {@link GDataParser} for the requested feed.
+ * @throws ParseException Thrown if the server response cannot be parsed.
+ * @throws IOException Thrown if an error occurs while communicating with
+ * the GData service.
+ * @throws HttpException Thrown if the http response contains a result other than 2xx
+ */
+ public GDataParser getParserForFeed(Class feedEntryClass, String feedUrl, String authToken)
+ throws ParseException, IOException, HttpException {
+ InputStream is = gDataClient.getFeedAsStream(feedUrl, authToken);
+ return gDataParserFactory.createParser(feedEntryClass, is);
+ }
+
+ /**
+ * Fetches a media entry as an InputStream. The caller is responsible for closing the
+ * returned {@link InputStream}.
+ *
+ * @param mediaEntryUrl The URL of the media entry that should be fetched.
+ * @param authToken The authentication token for this user.
+ * @return A {@link InputStream} for the requested media entry.
+ * @throws IOException Thrown if an error occurs while communicating with
+ * the GData service.
+ */
+ public InputStream getMediaEntryAsStream(String mediaEntryUrl, String authToken)
+ throws IOException, HttpException {
+ return gDataClient.getMediaEntryAsStream(mediaEntryUrl, authToken);
+ }
+
+ /**
+ * Creates a new entry at the provided feed. Parses the server response
+ * into the version of the entry stored on the server.
+ *
+ * @param feedUrl The feed where the entry should be created.
+ * @param authToken The authentication token for this user.
+ * @param entry The entry that should be created.
+ * @return The entry returned by the server as a result of creating the
+ * provided entry.
+ * @throws ParseException Thrown if the server response cannot be parsed.
+ * @throws IOException Thrown if an error occurs while communicating with
+ * the GData service.
+ * @throws HttpException if the service returns an error response
+ */
+ public Entry createEntry(String feedUrl, String authToken, Entry entry)
+ throws ParseException, IOException, HttpException {
+ GDataSerializer serializer = gDataParserFactory.createSerializer(entry);
+ InputStream is = gDataClient.createEntry(feedUrl, authToken, serializer);
+ return parseEntry(entry.getClass(), is);
+ }
+
+ /**
+ * Fetches an existing entry.
+ * @param entryClass the type of entry to expect
+ * @param id of the entry to fetch.
+ * @param authToken The authentication token for this user. @return The entry returned by the server.
+ * @throws ParseException Thrown if the server response cannot be parsed.
+ * @throws HttpException if the service returns an error response
+ * @throws IOException Thrown if an error occurs while communicating with
+ * the GData service.
+ * @return The entry returned by the server
+ */
+ public Entry getEntry(Class entryClass, String id, String authToken)
+ throws ParseException, IOException, HttpException {
+ InputStream is = getGDataClient().getFeedAsStream(id, authToken);
+ return parseEntry(entryClass, is);
+ }
+
+ /**
+ * Updates an existing entry. Parses the server response into the version
+ * of the entry stored on the server.
+ *
+ * @param entry The entry that should be updated.
+ * @param authToken The authentication token for this user.
+ * @return The entry returned by the server as a result of updating the
+ * provided entry.
+ * @throws ParseException Thrown if the server response cannot be parsed.
+ * @throws IOException Thrown if an error occurs while communicating with
+ * the GData service.
+ * @throws HttpException if the service returns an error response
+ */
+ public Entry updateEntry(Entry entry, String authToken)
+ throws ParseException, IOException, HttpException {
+ String editUri = entry.getEditUri();
+ if (StringUtils.isEmpty(editUri)) {
+ throw new ParseException("No edit URI -- cannot update.");
+ }
+
+ GDataSerializer serializer = gDataParserFactory.createSerializer(entry);
+ InputStream is = gDataClient.updateEntry(editUri, authToken, serializer);
+ return parseEntry(entry.getClass(), is);
+ }
+
+ /**
+ * Updates an existing entry. Parses the server response into the metadata
+ * of the entry stored on the server.
+ *
+ * @param editUri The URI of the resource that should be updated.
+ * @param inputStream The {@link java.io.InputStream} that contains the new value
+ * of the media entry
+ * @param contentType The content type of the new media entry
+ * @param authToken The authentication token for this user.
+ * @return The entry returned by the server as a result of updating the
+ * provided entry.
+ * @throws HttpException if the service returns an error response
+ * @throws ParseException Thrown if the server response cannot be parsed.
+ * @throws IOException Thrown if an error occurs while communicating with
+ * the GData service.
+ */
+ public MediaEntry updateMediaEntry(String editUri, InputStream inputStream, String contentType,
+ String authToken) throws IOException, HttpException, ParseException {
+ if (StringUtils.isEmpty(editUri)) {
+ throw new IllegalArgumentException("No edit URI -- cannot update.");
+ }
+
+ InputStream is = gDataClient.updateMediaEntry(editUri, authToken, inputStream, contentType);
+ return (MediaEntry)parseEntry(MediaEntry.class, is);
+ }
+
+ /**
+ * Deletes an existing entry.
+ *
+ * @param editUri The editUri for the entry that should be deleted.
+ * @param authToken The authentication token for this user.
+ * @throws IOException Thrown if an error occurs while communicating with
+ * the GData service.
+ * @throws HttpException if the service returns an error response
+ */
+ public void deleteEntry(String editUri, String authToken)
+ throws IOException, HttpException {
+ gDataClient.deleteEntry(editUri, authToken);
+ }
+
+ private Entry parseEntry(Class entryClass, InputStream is) throws ParseException, IOException {
+ GDataParser parser = null;
+ try {
+ parser = gDataParserFactory.createParser(entryClass, is);
+ return parser.parseStandaloneEntry();
+ } finally {
+ if (parser != null) {
+ parser.close();
+ }
+ }
+ }
+}
diff --git a/src/com/google/wireless/gdata/client/HttpException.java b/src/com/google/wireless/gdata/client/HttpException.java
new file mode 100644
index 0000000..48f1033
--- /dev/null
+++ b/src/com/google/wireless/gdata/client/HttpException.java
@@ -0,0 +1,58 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.client;
+
+import java.io.InputStream;
+
+/**
+ * A class representing exceptional (i.e., non 200) responses from an HTTP
+ * Server.
+ */
+public class HttpException extends Exception {
+
+ public static final int SC_BAD_REQUEST = 400;
+
+ public static final int SC_UNAUTHORIZED = 401;
+
+ public static final int SC_FORBIDDEN = 403;
+
+ public static final int SC_NOT_FOUND = 404;
+
+ public static final int SC_CONFLICT = 409;
+
+ public static final int SC_GONE = 410;
+
+ public static final int SC_INTERNAL_SERVER_ERROR = 500;
+
+ private final int statusCode;
+
+ private final InputStream responseStream;
+
+ /**
+ * Creates an HttpException with the given message, statusCode and
+ * responseStream.
+ */
+ //TODO: also record response headers?
+ public HttpException(String message, int statusCode,
+ InputStream responseStream) {
+ super(message);
+ this.statusCode = statusCode;
+ this.responseStream = responseStream;
+ }
+
+ /**
+ * Gets the status code associated with this exception.
+ * @return the status code returned by the server, typically one of the SC_*
+ * constants.
+ */
+ public int getStatusCode() {
+ return statusCode;
+ }
+
+ /**
+ * @return the error response stream from the server.
+ */
+ public InputStream getResponseStream() {
+ return responseStream;
+ }
+}
diff --git a/src/com/google/wireless/gdata/client/HttpQueryParams.java b/src/com/google/wireless/gdata/client/HttpQueryParams.java
new file mode 100644
index 0000000..4878002
--- /dev/null
+++ b/src/com/google/wireless/gdata/client/HttpQueryParams.java
@@ -0,0 +1,73 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.client;
+
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * A concrete implementation of QueryParams that uses the encodeUri method of a
+ * GDataClient (passed in at construction time) to URL encode parameters.
+ *
+ * This implementation maintains the order of parameters, which is useful for
+ * testing. Instances of this class are not thread safe.
+ */
+public class HttpQueryParams extends QueryParams {
+
+ private GDataClient client;
+
+ /* Used to store the mapping of names to values */
+ private Hashtable params;
+
+ /* Used to maintain the order of parameter additions */
+ private Vector names;
+
+ /**
+ * Constructs a new, empty HttpQueryParams.
+ *
+ * @param client GDataClient whose encodeUri method is used for URL encoding.
+ */
+ public HttpQueryParams(GDataClient client) {
+ this.client = client;
+ // We expect most queries to have a relatively small number of parameters.
+ names = new Vector(4);
+ params = new Hashtable(7);
+ }
+
+ public String generateQueryUrl(String feedUrl) {
+ StringBuffer url = new StringBuffer(feedUrl);
+ url.append(feedUrl.indexOf('?') >= 0 ? '&' : '?');
+
+ for (int i = 0; i < names.size(); i++) {
+ if (i > 0) {
+ url.append('&');
+ }
+ String name = (String) names.elementAt(i);
+ url.append(client.encodeUri(name)).append('=');
+ url.append(client.encodeUri(getParamValue(name)));
+ }
+ return url.toString();
+ }
+
+ public String getParamValue(String param) {
+ return (String) params.get(param);
+ }
+
+ public void setParamValue(String param, String value) {
+ if (value != null) {
+ if (!params.containsKey(param)) {
+ names.addElement(param);
+ }
+ params.put(param, value);
+ } else {
+ if (params.remove(param) != null) {
+ names.removeElement(param);
+ }
+ }
+ }
+
+ public void clear() {
+ names.removeAllElements();
+ params.clear();
+ }
+}
diff --git a/src/com/google/wireless/gdata/client/QueryParams.java b/src/com/google/wireless/gdata/client/QueryParams.java
new file mode 100644
index 0000000..30deff8
--- /dev/null
+++ b/src/com/google/wireless/gdata/client/QueryParams.java
@@ -0,0 +1,240 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.client;
+
+/**
+ * Class for specifying parameters and constraints for a GData feed.
+ * These are used to modify the feed URL and add querystring parameters to the
+ * feed URL.
+ *
+ * Note that if an entry ID has been set, no other query params can be set.
+ *
+ * @see QueryParams#generateQueryUrl(String)
+ */
+// TODO: add support for projections?
+// TODO: add support for categories?
+public abstract class QueryParams {
+
+ /**
+ * Param name constant for a search query.
+ */
+ public static final String QUERY_PARAM = "q";
+
+ /**
+ * Param name constant for filtering by author.
+ */
+ public static final String AUTHOR_PARAM = "author";
+
+ /**
+ * Param name constant for alternate representations of GData.
+ */
+ public static final String ALT_PARAM = "alt";
+ public static final String ALT_RSS = "rss";
+ public static final String ALT_JSON = "json";
+
+ /**
+ * Param name constant for the updated min.
+ */
+ public static final String UPDATED_MIN_PARAM = "updated-min";
+
+ /**
+ * Param name constant for the updated max.
+ */
+ public static final String UPDATED_MAX_PARAM = "updated-max";
+
+ /**
+ * Param name constant for the published min.
+ */
+ public static final String PUBLISHED_MIN_PARAM = "published-min";
+
+ /**
+ * Param name constant for the published max.
+ */
+ public static final String PUBLISHED_MAX_PARAM = "published-max";
+
+ /**
+ * Param name constant for the start index for results.
+ */
+ public static final String START_INDEX_PARAM = "start-index";
+
+ /**
+ * Param name constant for the max number of results that should be fetched.
+ */
+ public static final String MAX_RESULTS_PARAM = "max-results";
+
+ private String entryId;
+
+ /**
+ * Creates a new empty QueryParams.
+ */
+ public QueryParams() {
+ }
+
+ /**
+ * Generates the url that should be used to query a GData feed.
+ * @param feedUrl The original feed URL.
+ * @return The URL that should be used to query the GData feed.
+ */
+ public abstract String generateQueryUrl(String feedUrl);
+
+ /**
+ * Gets a parameter value from this QueryParams.
+ * @param param The parameter name.
+ * @return The parameter value. Returns null if the parameter is not
+ * defined in this QueryParams.
+ */
+ public abstract String getParamValue(String param);
+
+ /**
+ * Sets a parameter value in this QueryParams.
+ * @param param The parameter name.
+ * @param value The parameter value.
+ */
+ public abstract void setParamValue(String param, String value);
+
+ /**
+ * Clears everything in this QueryParams.
+ */
+ public abstract void clear();
+
+ /**
+ * @return the alt
+ */
+ public String getAlt() {
+ return getParamValue(ALT_PARAM);
+ }
+
+ /**
+ * @param alt the alt to set
+ */
+ public void setAlt(String alt) {
+ setParamValue(ALT_PARAM, alt);
+ }
+
+ /**
+ * @return the author
+ */
+ public String getAuthor() {
+ return getParamValue(AUTHOR_PARAM);
+ }
+
+ /**
+ * @param author the author to set
+ */
+ public void setAuthor(String author) {
+ setParamValue(AUTHOR_PARAM, author);
+ }
+
+ /**
+ * @return the entryId
+ */
+ public String getEntryId() {
+ return entryId;
+ }
+
+ /**
+ * @param entryId the entryId to set
+ */
+ public void setEntryId(String entryId) {
+ this.entryId = entryId;
+ }
+
+ /**
+ * @return the maxResults
+ */
+ public String getMaxResults() {
+ return getParamValue(MAX_RESULTS_PARAM);
+ }
+
+ // TODO: use an int!
+ /**
+ * @param maxResults the maxResults to set
+ */
+ public void setMaxResults(String maxResults) {
+ setParamValue(MAX_RESULTS_PARAM, maxResults);
+ }
+
+ /**
+ * @return the publishedMax
+ */
+ public String getPublishedMax() {
+ return getParamValue(PUBLISHED_MAX_PARAM);
+ }
+
+ /**
+ * @param publishedMax the publishedMax to set
+ */
+ public void setPublishedMax(String publishedMax) {
+ setParamValue(PUBLISHED_MAX_PARAM, publishedMax);
+ }
+
+ /**
+ * @return the publishedMin
+ */
+ public String getPublishedMin() {
+ return getParamValue(PUBLISHED_MIN_PARAM);
+ }
+
+ /**
+ * @param publishedMin the publishedMin to set
+ */
+ public void setPublishedMin(String publishedMin) {
+ setParamValue(PUBLISHED_MIN_PARAM, publishedMin);
+ }
+
+ /**
+ * @return the query
+ */
+ public String getQuery() {
+ return getParamValue(QUERY_PARAM);
+ }
+
+ /**
+ * @param query the query to set
+ */
+ public void setQuery(String query) {
+ setParamValue(QUERY_PARAM, query);
+ }
+
+ /**
+ * @return the startIndex
+ */
+ public String getStartIndex() {
+ return getParamValue(START_INDEX_PARAM);
+ }
+
+ /**
+ * @param startIndex the startIndex to set
+ */
+ public void setStartIndex(String startIndex) {
+ setParamValue(START_INDEX_PARAM, startIndex);
+ }
+
+ /**
+ * @return the updatedMax
+ */
+ public String getUpdatedMax() {
+ return getParamValue(UPDATED_MAX_PARAM);
+ }
+
+ /**
+ * @param updatedMax the updatedMax to set
+ */
+ public void setUpdatedMax(String updatedMax) {
+ setParamValue(UPDATED_MAX_PARAM, updatedMax);
+ }
+
+ /**
+ * @return the updatedMin
+ */
+ public String getUpdatedMin() {
+ return getParamValue(UPDATED_MIN_PARAM);
+ }
+
+ /**
+ * @param updatedMin the updatedMin to set
+ */
+ public void setUpdatedMin(String updatedMin) {
+ setParamValue(UPDATED_MIN_PARAM, updatedMin);
+ }
+}
diff --git a/src/com/google/wireless/gdata/client/package.html b/src/com/google/wireless/gdata/client/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/client/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/contacts/client/ContactsClient.java b/src/com/google/wireless/gdata/contacts/client/ContactsClient.java
new file mode 100644
index 0000000..dd62745
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/client/ContactsClient.java
@@ -0,0 +1,33 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.contacts.client;
+
+import com.google.wireless.gdata.client.GDataClient;
+import com.google.wireless.gdata.client.GDataParserFactory;
+import com.google.wireless.gdata.client.GDataServiceClient;
+
+/**
+ * GDataServiceClient for accessing Google Contacts. This client can access and
+ * parse the contacts feeds for specific user. The parser this class uses handle
+ * the XML version of feeds.
+ */
+public class ContactsClient extends GDataServiceClient {
+ /** Service value for contacts. */
+ public static final String SERVICE = "cp";
+
+ /**
+ * Create a new ContactsClient.
+ * @param client The GDataClient that should be used to authenticate
+ * if we are using the caribou feed
+ */
+ public ContactsClient(GDataClient client, GDataParserFactory factory) {
+ super(client, factory);
+ }
+
+ /* (non-Javadoc)
+ * @see GDataServiceClient#getServiceName
+ */
+ public String getServiceName() {
+ return SERVICE;
+ }
+}
diff --git a/src/com/google/wireless/gdata/contacts/client/package.html b/src/com/google/wireless/gdata/contacts/client/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/client/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/contacts/data/ContactEntry.java b/src/com/google/wireless/gdata/contacts/data/ContactEntry.java
new file mode 100644
index 0000000..2d3246e
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/data/ContactEntry.java
@@ -0,0 +1,218 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.contacts.data;
+
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.ExtendedProperty;
+import com.google.wireless.gdata.data.StringUtils;
+import com.google.wireless.gdata.parser.ParseException;
+
+import java.util.Vector;
+import java.util.Enumeration;
+
+/**
+ * Entry containing information about a contact.
+ */
+public class ContactEntry extends Entry {
+ private String linkPhotoHref;
+ private String linkEditPhotoHref;
+ private String linkPhotoType;
+ private String linkEditPhotoType;
+ private final Vector emailAddresses = new Vector();
+ private final Vector imAddresses = new Vector();
+ private final Vector phoneNumbers = new Vector();
+ private final Vector postalAddresses = new Vector();
+ private final Vector organizations = new Vector();
+ private final Vector extendedProperties = new Vector();
+ private final Vector groups = new Vector();
+
+ public ContactEntry() {
+ super();
+ }
+
+ public void setLinkEditPhoto(String href, String type) {
+ this.linkEditPhotoHref = href;
+ this.linkEditPhotoType = type;
+ }
+
+ public String getLinkEditPhotoHref() {
+ return linkEditPhotoHref;
+ }
+
+ public String getLinkEditPhotoType() {
+ return linkEditPhotoType;
+ }
+
+ public void setLinkPhoto(String href, String type) {
+ this.linkPhotoHref = href;
+ this.linkPhotoType = type;
+ }
+
+ public String getLinkPhotoHref() {
+ return linkPhotoHref;
+ }
+
+ public String getLinkPhotoType() {
+ return linkPhotoType;
+ }
+
+ public void addEmailAddress(EmailAddress emailAddress) {
+ emailAddresses.addElement(emailAddress);
+ }
+
+ public Vector getEmailAddresses() {
+ return emailAddresses;
+ }
+
+ public void addImAddress(ImAddress imAddress) {
+ imAddresses.addElement(imAddress);
+ }
+
+ public Vector getImAddresses() {
+ return imAddresses;
+ }
+
+ public void addPostalAddress(PostalAddress postalAddress) {
+ postalAddresses.addElement(postalAddress);
+ }
+
+ public Vector getPostalAddresses() {
+ return postalAddresses;
+ }
+
+ public void addPhoneNumber(PhoneNumber phoneNumber) {
+ phoneNumbers.addElement(phoneNumber);
+ }
+
+ public Vector getPhoneNumbers() {
+ return phoneNumbers;
+ }
+
+ public void addOrganization(Organization organization) {
+ organizations.addElement(organization);
+ }
+
+ public Vector getExtendedProperties() {
+ return extendedProperties;
+ }
+
+ public void addExtendedProperty(ExtendedProperty extendedProperty) {
+ extendedProperties.addElement(extendedProperty);
+ }
+
+ public Vector getGroups() {
+ return groups;
+ }
+
+ public void addGroup(GroupMembershipInfo group) {
+ groups.addElement(group);
+ }
+
+ public Vector getOrganizations() {
+ return organizations;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.wireless.gdata.data.Entry#clear()
+ */
+ public void clear() {
+ super.clear();
+ linkEditPhotoHref = null;
+ linkEditPhotoType = null;
+ linkPhotoHref = null;
+ linkPhotoType = null;
+ emailAddresses.removeAllElements();
+ imAddresses.removeAllElements();
+ phoneNumbers.removeAllElements();
+ postalAddresses.removeAllElements();
+ organizations.removeAllElements();
+ extendedProperties.removeAllElements();
+ groups.removeAllElements();
+ }
+
+ protected void toString(StringBuffer sb) {
+ super.toString(sb);
+ sb.append("\n");
+ sb.append("ContactEntry:");
+ if (!StringUtils.isEmpty(linkPhotoHref)) {
+ sb.append(" linkPhotoHref:").append(linkPhotoHref).append("\n");
+ }
+ if (!StringUtils.isEmpty(linkPhotoType)) {
+ sb.append(" linkPhotoType:").append(linkPhotoType).append("\n");
+ }
+ if (!StringUtils.isEmpty(linkEditPhotoHref)) {
+ sb.append(" linkEditPhotoHref:").append(linkEditPhotoHref).append("\n");
+ }
+ if (!StringUtils.isEmpty(linkEditPhotoType)) {
+ sb.append(" linkEditPhotoType:").append(linkEditPhotoType).append("\n");
+ }
+ for (Enumeration iter = emailAddresses.elements();
+ iter.hasMoreElements(); ) {
+ sb.append(" ");
+ ((EmailAddress) iter.nextElement()).toString(sb);
+ sb.append("\n");
+ }
+ for (Enumeration iter = imAddresses.elements();
+ iter.hasMoreElements(); ) {
+ sb.append(" ");
+ ((ImAddress) iter.nextElement()).toString(sb);
+ sb.append("\n");
+ }
+ for (Enumeration iter = postalAddresses.elements();
+ iter.hasMoreElements(); ) {
+ sb.append(" ");
+ ((PostalAddress) iter.nextElement()).toString(sb);
+ sb.append("\n");
+ }
+ for (Enumeration iter = phoneNumbers.elements();
+ iter.hasMoreElements(); ) {
+ sb.append(" ");
+ ((PhoneNumber) iter.nextElement()).toString(sb);
+ sb.append("\n");
+ }
+ for (Enumeration iter = organizations.elements();
+ iter.hasMoreElements(); ) {
+ sb.append(" ");
+ ((Organization) iter.nextElement()).toString(sb);
+ sb.append("\n");
+ }
+ for (Enumeration iter = extendedProperties.elements();
+ iter.hasMoreElements(); ) {
+ sb.append(" ");
+ ((ExtendedProperty) iter.nextElement()).toString(sb);
+ sb.append("\n");
+ }
+ for (Enumeration iter = groups.elements();
+ iter.hasMoreElements(); ) {
+ sb.append(" ");
+ ((GroupMembershipInfo) iter.nextElement()).toString(sb);
+ sb.append("\n");
+ }
+ }
+
+ public void validate() throws ParseException {
+ super.validate();
+ for (Enumeration iter = emailAddresses.elements(); iter.hasMoreElements(); ) {
+ ((EmailAddress) iter.nextElement()).validate();
+ }
+ for (Enumeration iter = imAddresses.elements(); iter.hasMoreElements(); ) {
+ ((ImAddress) iter.nextElement()).validate();
+ }
+ for (Enumeration iter = postalAddresses.elements(); iter.hasMoreElements(); ) {
+ ((PostalAddress) iter.nextElement()).validate();
+ }
+ for (Enumeration iter = phoneNumbers.elements(); iter.hasMoreElements(); ) {
+ ((PhoneNumber) iter.nextElement()).validate();
+ }
+ for (Enumeration iter = organizations.elements(); iter.hasMoreElements(); ) {
+ ((Organization) iter.nextElement()).validate();
+ }
+ for (Enumeration iter = extendedProperties.elements(); iter.hasMoreElements(); ) {
+ ((ExtendedProperty) iter.nextElement()).validate();
+ }
+ for (Enumeration iter = groups.elements(); iter.hasMoreElements(); ) {
+ ((GroupMembershipInfo) iter.nextElement()).validate();
+ }
+ }
+}
diff --git a/src/com/google/wireless/gdata/contacts/data/ContactsElement.java b/src/com/google/wireless/gdata/contacts/data/ContactsElement.java
new file mode 100644
index 0000000..8257fd6
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/data/ContactsElement.java
@@ -0,0 +1,71 @@
+package com.google.wireless.gdata.contacts.data;
+
+import com.google.wireless.gdata.parser.ParseException;
+
+/**
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Contains attributes that are common to all elements in a ContactEntry.
+ */
+public abstract class ContactsElement {
+ public static final byte TYPE_NONE = -1;
+ private byte type = TYPE_NONE;
+
+ private String label;
+
+ private boolean isPrimary;
+
+ public boolean isPrimary() {
+ return isPrimary;
+ }
+
+ public void setIsPrimary(boolean primary) {
+ isPrimary = primary;
+ }
+
+ public byte getType() {
+ return type;
+ }
+
+ public void setType(byte rel) {
+ this.type = rel;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public void toString(StringBuffer sb) {
+ sb.append(" type:").append(type);
+ sb.append(" isPrimary:").append(isPrimary);
+ if (label != null) sb.append(" label:").append(label);
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ toString(sb);
+ return sb.toString();
+ }
+
+ public void validate() throws ParseException {
+ if ((label == null && type == TYPE_NONE) || (label != null && type != TYPE_NONE)) {
+ throw new ParseException("exactly one of label or type must be set");
+ }
+ }
+}
diff --git a/src/com/google/wireless/gdata/contacts/data/ContactsFeed.java b/src/com/google/wireless/gdata/contacts/data/ContactsFeed.java
new file mode 100644
index 0000000..ce75c94
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/data/ContactsFeed.java
@@ -0,0 +1,16 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.contacts.data;
+
+import com.google.wireless.gdata.data.Feed;
+
+/**
+ * Feed containing contacts.
+ */
+public class ContactsFeed extends Feed {
+ /**
+ * Creates a new empty events feed.
+ */
+ public ContactsFeed() {
+ }
+}
diff --git a/src/com/google/wireless/gdata/contacts/data/EmailAddress.java b/src/com/google/wireless/gdata/contacts/data/EmailAddress.java
new file mode 100644
index 0000000..dae409e
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/data/EmailAddress.java
@@ -0,0 +1,28 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.contacts.data;
+
+/**
+ * The EmailAddress GData type.
+ */
+public class EmailAddress extends ContactsElement {
+ public static final byte TYPE_HOME = 1;
+ public static final byte TYPE_WORK = 2;
+ public static final byte TYPE_OTHER = 3;
+
+ private String address;
+
+ public String getAddress() {
+ return address;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public void toString(StringBuffer sb) {
+ sb.append("EmailAddress");
+ super.toString(sb);
+ if (address != null) sb.append(" address:").append(address);
+ }
+}
diff --git a/src/com/google/wireless/gdata/contacts/data/GeoPt.java b/src/com/google/wireless/gdata/contacts/data/GeoPt.java
new file mode 100644
index 0000000..d6f7c68
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/data/GeoPt.java
@@ -0,0 +1,70 @@
+package com.google.wireless.gdata.contacts.data;
+
+/**
+ * The GeoPt GData type.
+ */
+public class GeoPt {
+ private String label;
+ private Float latitude;
+ private Float longitude;
+ private Float elevation;
+
+ // TODO: figure out how to store the GeoPt time
+ private String time;
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public Float getLatitute() {
+ return latitude;
+ }
+
+ public void setLatitude(Float lat) {
+ this.latitude = lat;
+ }
+
+ public Float getLongitute() {
+ return longitude;
+ }
+
+ public void setLongitude(Float lon) {
+ this.longitude = lon;
+ }
+
+ public Float getElevation() {
+ return elevation;
+ }
+
+ public void setElevation(Float elev) {
+ this.elevation = elev;
+ }
+
+ public String getTime() {
+ return time;
+ }
+
+ public void setTime(String time) {
+ this.time = time;
+ }
+
+ public void toString(StringBuffer sb) {
+ sb.append("GeoPt");
+ if (latitude != null) sb.append(" latitude:").append(latitude);
+ if (longitude != null) sb.append(" longitude:").append(longitude);
+ if (elevation != null) sb.append(" elevation:").append(elevation);
+ if (time != null) sb.append(" time:").append(time);
+ if (label != null) sb.append(" label:").append(label);
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ toString(sb);
+ return sb.toString();
+ }
+}
diff --git a/src/com/google/wireless/gdata/contacts/data/GroupEntry.java b/src/com/google/wireless/gdata/contacts/data/GroupEntry.java
new file mode 100644
index 0000000..2218995
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/data/GroupEntry.java
@@ -0,0 +1,39 @@
+package com.google.wireless.gdata.contacts.data;
+
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.StringUtils;
+
+/**
+ * Entry containing information about a contact group.
+ */
+public class GroupEntry extends Entry {
+ // If this is a system group then this field will be set with the name of the system group.
+ private String systemGroup = null;
+
+ public GroupEntry() {
+ super();
+ }
+
+ public String getSystemGroup() {
+ return systemGroup;
+ }
+
+ @Override
+ public void clear() {
+ super.clear();
+ systemGroup = null;
+ }
+
+ public void setSystemGroup(String systemGroup) {
+ this.systemGroup = systemGroup;
+ }
+
+ protected void toString(StringBuffer sb) {
+ super.toString(sb);
+ sb.append("\n");
+ sb.append("GroupEntry:");
+ if (!StringUtils.isEmpty(systemGroup)) {
+ sb.append(" systemGroup:").append(systemGroup).append("\n");
+ }
+ }
+}
diff --git a/src/com/google/wireless/gdata/contacts/data/GroupMembershipInfo.java b/src/com/google/wireless/gdata/contacts/data/GroupMembershipInfo.java
new file mode 100644
index 0000000..e327ffe
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/data/GroupMembershipInfo.java
@@ -0,0 +1,38 @@
+package com.google.wireless.gdata.contacts.data;
+
+import com.google.wireless.gdata.data.StringUtils;
+import com.google.wireless.gdata.parser.ParseException;
+
+/** The groupMembershipInfo GData type. */
+public class GroupMembershipInfo {
+ private String group;
+ private boolean deleted;
+
+ public String getGroup() {
+ return group;
+ }
+
+ public void setGroup(String group) {
+ this.group = group;
+ }
+
+ public boolean isDeleted() {
+ return deleted;
+ }
+
+ public void setDeleted(boolean deleted) {
+ this.deleted = deleted;
+ }
+
+ public void toString(StringBuffer sb) {
+ sb.append("GroupMembershipInfo");
+ if (group != null) sb.append(" group:").append(group);
+ sb.append(" deleted:").append(deleted);
+ }
+
+ public void validate() throws ParseException {
+ if (StringUtils.isEmpty(group)) {
+ throw new ParseException("the group must be present");
+ }
+ }
+}
diff --git a/src/com/google/wireless/gdata/contacts/data/GroupsFeed.java b/src/com/google/wireless/gdata/contacts/data/GroupsFeed.java
new file mode 100644
index 0000000..b879e35
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/data/GroupsFeed.java
@@ -0,0 +1,14 @@
+package com.google.wireless.gdata.contacts.data;
+
+import com.google.wireless.gdata.data.Feed;
+
+/**
+ * Feed containing contact groups.
+ */
+public class GroupsFeed extends Feed {
+ /**
+ * Creates a new empty contact groups feed.
+ */
+ public GroupsFeed() {
+ }
+}
diff --git a/src/com/google/wireless/gdata/contacts/data/ImAddress.java b/src/com/google/wireless/gdata/contacts/data/ImAddress.java
new file mode 100644
index 0000000..291b4fa
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/data/ImAddress.java
@@ -0,0 +1,59 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.contacts.data;
+
+/**
+ * The ImAddress gdata type
+ */
+public class ImAddress extends ContactsElement {
+ public static final byte TYPE_HOME = 1;
+ public static final byte TYPE_WORK = 2;
+ public static final byte TYPE_OTHER = 3;
+
+ public static final byte PROTOCOL_CUSTOM = 1;
+ public static final byte PROTOCOL_AIM = 2;
+ public static final byte PROTOCOL_MSN = 3;
+ public static final byte PROTOCOL_YAHOO = 4;
+ public static final byte PROTOCOL_SKYPE = 5;
+ public static final byte PROTOCOL_QQ = 6;
+ public static final byte PROTOCOL_GOOGLE_TALK = 7;
+ public static final byte PROTOCOL_ICQ = 8;
+ public static final byte PROTOCOL_JABBER = 9;
+ public static final byte PROTOCOL_NONE = 10;
+
+ private byte protocolPredefined;
+ private String protocolCustom;
+ private String address;
+
+ public byte getProtocolPredefined() {
+ return protocolPredefined;
+ }
+
+ public void setProtocolPredefined(byte protocolPredefined) {
+ this.protocolPredefined = protocolPredefined;
+ }
+
+ public String getProtocolCustom() {
+ return protocolCustom;
+ }
+
+ public void setProtocolCustom(String protocolCustom) {
+ this.protocolCustom = protocolCustom;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public void toString(StringBuffer sb) {
+ sb.append("ImAddress");
+ super.toString(sb);
+ sb.append(" protocolPredefined:").append(protocolPredefined);
+ if (protocolCustom != null) sb.append(" protocolCustom:").append(protocolCustom);
+ if (address != null) sb.append(" address:").append(address);
+ }
+}
diff --git a/src/com/google/wireless/gdata/contacts/data/Organization.java b/src/com/google/wireless/gdata/contacts/data/Organization.java
new file mode 100644
index 0000000..ed6736b
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/data/Organization.java
@@ -0,0 +1,43 @@
+package com.google.wireless.gdata.contacts.data;
+
+import com.google.wireless.gdata.parser.ParseException;
+
+/** The Organization GData type. */
+public class Organization extends ContactsElement {
+ public static final byte TYPE_WORK = 1;
+ public static final byte TYPE_OTHER = 2;
+
+ private String name;
+ private String title;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public void toString(StringBuffer sb) {
+ sb.append("Organization");
+ super.toString(sb);
+ if (name != null) sb.append(" name:").append(name);
+ if (title != null) sb.append(" title:").append(title);
+ }
+
+ public void validate() throws ParseException {
+ super.validate();
+
+ if (name == null && title == null) {
+ throw new ParseException("at least one of name or title must be present");
+ }
+ }
+}
diff --git a/src/com/google/wireless/gdata/contacts/data/PhoneNumber.java b/src/com/google/wireless/gdata/contacts/data/PhoneNumber.java
new file mode 100644
index 0000000..add741a
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/data/PhoneNumber.java
@@ -0,0 +1,33 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.contacts.data;
+
+/**
+ * The PhoneNumber gdata type
+ */
+public class PhoneNumber extends ContactsElement {
+ /** The phone number type. */
+ public static final byte TYPE_MOBILE = 1;
+ public static final byte TYPE_HOME = 2;
+ public static final byte TYPE_WORK = 3;
+ public static final byte TYPE_WORK_FAX = 4;
+ public static final byte TYPE_HOME_FAX = 5;
+ public static final byte TYPE_PAGER = 6;
+ public static final byte TYPE_OTHER = 7;
+
+ private String phoneNumber;
+
+ public String getPhoneNumber() {
+ return phoneNumber;
+ }
+
+ public void setPhoneNumber(String phoneNumber) {
+ this.phoneNumber = phoneNumber;
+ }
+
+ public void toString(StringBuffer sb) {
+ sb.append("PhoneNumber");
+ super.toString(sb);
+ if (phoneNumber != null) sb.append(" phoneNumber:").append(phoneNumber);
+ }
+}
diff --git a/src/com/google/wireless/gdata/contacts/data/PostalAddress.java b/src/com/google/wireless/gdata/contacts/data/PostalAddress.java
new file mode 100644
index 0000000..f9d6522
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/data/PostalAddress.java
@@ -0,0 +1,28 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.contacts.data;
+
+/**
+ * The PostalAddress gdata type
+ */
+public class PostalAddress extends ContactsElement {
+ public static final byte TYPE_HOME = 1;
+ public static final byte TYPE_WORK = 2;
+ public static final byte TYPE_OTHER = 3;
+
+ private String value;
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public void toString(StringBuffer sb) {
+ sb.append("PostalAddress");
+ super.toString(sb);
+ if (value != null) sb.append(" value:").append(value);
+ }
+}
diff --git a/src/com/google/wireless/gdata/contacts/data/package.html b/src/com/google/wireless/gdata/contacts/data/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/data/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/contacts/package.html b/src/com/google/wireless/gdata/contacts/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/contacts/parser/package.html b/src/com/google/wireless/gdata/contacts/parser/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/parser/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/contacts/parser/xml/XmlContactsGDataParser.java b/src/com/google/wireless/gdata/contacts/parser/xml/XmlContactsGDataParser.java
new file mode 100644
index 0000000..5c7f551
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/parser/xml/XmlContactsGDataParser.java
@@ -0,0 +1,302 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.contacts.parser.xml;
+
+import com.google.wireless.gdata.contacts.data.ContactEntry;
+import com.google.wireless.gdata.contacts.data.ContactsElement;
+import com.google.wireless.gdata.contacts.data.ContactsFeed;
+import com.google.wireless.gdata.contacts.data.EmailAddress;
+import com.google.wireless.gdata.contacts.data.ImAddress;
+import com.google.wireless.gdata.contacts.data.Organization;
+import com.google.wireless.gdata.contacts.data.PhoneNumber;
+import com.google.wireless.gdata.contacts.data.PostalAddress;
+import com.google.wireless.gdata.contacts.data.GroupMembershipInfo;
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.Feed;
+import com.google.wireless.gdata.data.XmlUtils;
+import com.google.wireless.gdata.data.ExtendedProperty;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.parser.xml.XmlGDataParser;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Hashtable;
+import java.util.Enumeration;
+
+/**
+ * GDataParser for a contacts feed.
+ */
+public class XmlContactsGDataParser extends XmlGDataParser {
+ /** Namespace prefix for Contacts */
+ public static final String NAMESPACE_CONTACTS = "gContact";
+
+ /** Namespace URI for Contacts */
+ public static final String NAMESPACE_CONTACTS_URI =
+ "http://schemas.google.com/contact/2008";
+
+ /** The photo link rels */
+ public static final String LINK_REL_PHOTO = "http://schemas.google.com/contacts/2008/rel#photo";
+ public static final String LINK_REL_EDIT_PHOTO =
+ "http://schemas.google.com/contacts/2008/rel#edit-photo";
+
+ /** The phone number type gdata string. */
+ private static final String GD_NAMESPACE = "http://schemas.google.com/g/2005#";
+ public static final String TYPESTRING_MOBILE = GD_NAMESPACE + "mobile";
+ public static final String TYPESTRING_HOME = GD_NAMESPACE + "home";
+ public static final String TYPESTRING_WORK = GD_NAMESPACE + "work";
+ public static final String TYPESTRING_HOME_FAX = GD_NAMESPACE + "home_fax";
+ public static final String TYPESTRING_WORK_FAX = GD_NAMESPACE + "work_fax";
+ public static final String TYPESTRING_PAGER = GD_NAMESPACE + "pager";
+ public static final String TYPESTRING_OTHER = GD_NAMESPACE + "other";
+
+ public static final String IM_PROTOCOL_AIM = GD_NAMESPACE + "AIM";
+ public static final String IM_PROTOCOL_MSN = GD_NAMESPACE + "MSN";
+ public static final String IM_PROTOCOL_YAHOO = GD_NAMESPACE + "YAHOO";
+ public static final String IM_PROTOCOL_SKYPE = GD_NAMESPACE + "SKYPE";
+ public static final String IM_PROTOCOL_QQ = GD_NAMESPACE + "QQ";
+ public static final String IM_PROTOCOL_GOOGLE_TALK = GD_NAMESPACE + "GOOGLE_TALK";
+ public static final String IM_PROTOCOL_ICQ = GD_NAMESPACE + "ICQ";
+ public static final String IM_PROTOCOL_JABBER = GD_NAMESPACE + "JABBER";
+
+ private static final Hashtable REL_TO_TYPE_EMAIL;
+ private static final Hashtable REL_TO_TYPE_PHONE;
+ private static final Hashtable REL_TO_TYPE_POSTAL;
+ private static final Hashtable REL_TO_TYPE_IM;
+ private static final Hashtable REL_TO_TYPE_ORGANIZATION;
+ private static final Hashtable IM_PROTOCOL_STRING_TO_TYPE_MAP;
+
+ public static final Hashtable TYPE_TO_REL_EMAIL;
+ public static final Hashtable TYPE_TO_REL_PHONE;
+ public static final Hashtable TYPE_TO_REL_POSTAL;
+ public static final Hashtable TYPE_TO_REL_IM;
+ public static final Hashtable TYPE_TO_REL_ORGANIZATION;
+ public static final Hashtable IM_PROTOCOL_TYPE_TO_STRING_MAP;
+
+ static {
+ Hashtable map;
+
+ map = new Hashtable();
+ map.put(TYPESTRING_HOME, new Byte(EmailAddress.TYPE_HOME));
+ map.put(TYPESTRING_WORK, new Byte(EmailAddress.TYPE_WORK));
+ map.put(TYPESTRING_OTHER, new Byte(EmailAddress.TYPE_OTHER));
+ // TODO: this is a hack to support the old feed
+ map.put(GD_NAMESPACE + "primary", (byte)4);
+ REL_TO_TYPE_EMAIL = map;
+ TYPE_TO_REL_EMAIL = swapMap(map);
+
+ map = new Hashtable();
+ map.put(TYPESTRING_HOME, new Byte(PhoneNumber.TYPE_HOME));
+ map.put(TYPESTRING_MOBILE, new Byte(PhoneNumber.TYPE_MOBILE));
+ map.put(TYPESTRING_PAGER, new Byte(PhoneNumber.TYPE_PAGER));
+ map.put(TYPESTRING_WORK, new Byte(PhoneNumber.TYPE_WORK));
+ map.put(TYPESTRING_HOME_FAX, new Byte(PhoneNumber.TYPE_HOME_FAX));
+ map.put(TYPESTRING_WORK_FAX, new Byte(PhoneNumber.TYPE_WORK_FAX));
+ map.put(TYPESTRING_OTHER, new Byte(PhoneNumber.TYPE_OTHER));
+ REL_TO_TYPE_PHONE = map;
+ TYPE_TO_REL_PHONE = swapMap(map);
+
+ map = new Hashtable();
+ map.put(TYPESTRING_HOME, new Byte(PostalAddress.TYPE_HOME));
+ map.put(TYPESTRING_WORK, new Byte(PostalAddress.TYPE_WORK));
+ map.put(TYPESTRING_OTHER, new Byte(PostalAddress.TYPE_OTHER));
+ REL_TO_TYPE_POSTAL = map;
+ TYPE_TO_REL_POSTAL = swapMap(map);
+
+ map = new Hashtable();
+ map.put(TYPESTRING_HOME, new Byte(ImAddress.TYPE_HOME));
+ map.put(TYPESTRING_WORK, new Byte(ImAddress.TYPE_WORK));
+ map.put(TYPESTRING_OTHER, new Byte(ImAddress.TYPE_OTHER));
+ REL_TO_TYPE_IM = map;
+ TYPE_TO_REL_IM = swapMap(map);
+
+ map = new Hashtable();
+ map.put(TYPESTRING_WORK, new Byte(Organization.TYPE_WORK));
+ map.put(TYPESTRING_OTHER, new Byte(Organization.TYPE_OTHER));
+ REL_TO_TYPE_ORGANIZATION = map;
+ TYPE_TO_REL_ORGANIZATION = swapMap(map);
+
+ map = new Hashtable();
+ map.put(IM_PROTOCOL_AIM, new Byte(ImAddress.PROTOCOL_AIM));
+ map.put(IM_PROTOCOL_MSN, new Byte(ImAddress.PROTOCOL_MSN));
+ map.put(IM_PROTOCOL_YAHOO, new Byte(ImAddress.PROTOCOL_YAHOO));
+ map.put(IM_PROTOCOL_SKYPE, new Byte(ImAddress.PROTOCOL_SKYPE));
+ map.put(IM_PROTOCOL_QQ, new Byte(ImAddress.PROTOCOL_QQ));
+ map.put(IM_PROTOCOL_GOOGLE_TALK, new Byte(ImAddress.PROTOCOL_GOOGLE_TALK));
+ map.put(IM_PROTOCOL_ICQ, new Byte(ImAddress.PROTOCOL_ICQ));
+ map.put(IM_PROTOCOL_JABBER, new Byte(ImAddress.PROTOCOL_JABBER));
+ IM_PROTOCOL_STRING_TO_TYPE_MAP = map;
+ IM_PROTOCOL_TYPE_TO_STRING_MAP = swapMap(map);
+ }
+
+ private static Hashtable swapMap(Hashtable originalMap) {
+ Hashtable newMap = new Hashtable();
+ Enumeration enumeration = originalMap.keys();
+ while (enumeration.hasMoreElements()) {
+ Object key = enumeration.nextElement();
+ Object value = originalMap.get(key);
+ if (newMap.containsKey(value)) {
+ throw new IllegalArgumentException("value " + value
+ + " was already encountered");
+ }
+ newMap.put(value, key);
+ }
+ return newMap;
+ }
+
+ /**
+ * Creates a new XmlEventsGDataParser.
+ * @param is The InputStream that should be parsed.
+ * @throws ParseException Thrown if a parser cannot be created.
+ */
+ public XmlContactsGDataParser(InputStream is, XmlPullParser parser)
+ throws ParseException {
+ super(is, parser);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.wireless.gdata.parser.xml.XmlGDataParser#createFeed()
+ */
+ protected Feed createFeed() {
+ return new ContactsFeed();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.wireless.gdata.parser.xml.XmlGDataParser#createEntry()
+ */
+ protected Entry createEntry() {
+ return new ContactEntry();
+ }
+
+ protected void handleExtraElementInEntry(Entry entry) throws XmlPullParserException, IOException {
+ XmlPullParser parser = getParser();
+
+ if (!(entry instanceof ContactEntry)) {
+ throw new IllegalArgumentException("Expected ContactEntry!");
+ }
+ ContactEntry contactEntry = (ContactEntry) entry;
+ String name = parser.getName();
+ if ("email".equals(name)) {
+ EmailAddress emailAddress = new EmailAddress();
+ parseContactsElement(emailAddress, parser, REL_TO_TYPE_EMAIL);
+ // TODO: remove this when the feed is upgraded
+ if (emailAddress.getType() == 4) {
+ emailAddress.setType(EmailAddress.TYPE_OTHER);
+ emailAddress.setIsPrimary(true);
+ emailAddress.setLabel(null);
+ }
+ emailAddress.setAddress(parser.getAttributeValue(null /* ns */, "address"));
+ contactEntry.addEmailAddress(emailAddress);
+ } else if ("deleted".equals(name)) {
+ contactEntry.setDeleted(true);
+ } else if ("im".equals(name)) {
+ ImAddress imAddress = new ImAddress();
+ parseContactsElement(imAddress, parser, REL_TO_TYPE_IM);
+ imAddress.setAddress(parser.getAttributeValue(null /* ns */, "address"));
+ imAddress.setLabel(parser.getAttributeValue(null /* ns */, "label"));
+ String protocolString = parser.getAttributeValue(null /* ns */, "protocol");
+ if (protocolString == null) {
+ imAddress.setProtocolPredefined(ImAddress.PROTOCOL_NONE);
+ imAddress.setProtocolCustom(null);
+ } else {
+ Byte predefinedProtocol = (Byte) IM_PROTOCOL_STRING_TO_TYPE_MAP.get(protocolString);
+ if (predefinedProtocol == null) {
+ imAddress.setProtocolPredefined(ImAddress.PROTOCOL_CUSTOM);
+ imAddress.setProtocolCustom(protocolString);
+ } else {
+ imAddress.setProtocolPredefined(predefinedProtocol.byteValue());
+ imAddress.setProtocolCustom(null);
+ }
+ }
+ contactEntry.addImAddress(imAddress);
+ } else if ("postalAddress".equals(name)) {
+ PostalAddress postalAddress = new PostalAddress();
+ parseContactsElement(postalAddress, parser, REL_TO_TYPE_POSTAL);
+ postalAddress.setValue(XmlUtils.extractChildText(parser));
+ contactEntry.addPostalAddress(postalAddress);
+ } else if ("phoneNumber".equals(name)) {
+ PhoneNumber phoneNumber = new PhoneNumber();
+ parseContactsElement(phoneNumber, parser, REL_TO_TYPE_PHONE);
+ phoneNumber.setPhoneNumber(XmlUtils.extractChildText(parser));
+ contactEntry.addPhoneNumber(phoneNumber);
+ } else if ("organization".equals(name)) {
+ Organization organization = new Organization();
+ parseContactsElement(organization, parser, REL_TO_TYPE_ORGANIZATION);
+ handleOrganizationSubElement(organization, parser);
+ contactEntry.addOrganization(organization);
+ } else if ("extendedProperty".equals(name)) {
+ ExtendedProperty extendedProperty = new ExtendedProperty();
+ parseExtendedProperty(extendedProperty);
+ contactEntry.addExtendedProperty(extendedProperty);
+ } else if ("groupMembershipInfo".equals(name)) {
+ GroupMembershipInfo group = new GroupMembershipInfo();
+ group.setGroup(parser.getAttributeValue(null /* ns */, "href"));
+ group.setDeleted("true".equals(parser.getAttributeValue(null /* ns */, "deleted")));
+ contactEntry.addGroup(group);
+ }
+ }
+
+ @Override
+ protected void handleExtraLinkInEntry(String rel, String type, String href, Entry entry)
+ throws XmlPullParserException, IOException {
+ if (LINK_REL_PHOTO.equals(rel)) {
+ ContactEntry contactEntry = (ContactEntry) entry;
+ contactEntry.setLinkPhoto(href, type);
+ } else if (LINK_REL_EDIT_PHOTO.equals(rel)) {
+ ContactEntry contactEntry = (ContactEntry) entry;
+ contactEntry.setLinkEditPhoto(href, type);
+ }
+ }
+
+ private static void parseContactsElement(ContactsElement element, XmlPullParser parser,
+ Hashtable relToTypeMap) throws XmlPullParserException {
+ String rel = parser.getAttributeValue(null /* ns */, "rel");
+ String label = parser.getAttributeValue(null /* ns */, "label");
+
+ if ((label == null && rel == null) || (label != null && rel != null)) {
+ // TODO: remove this once the focus feed is fixed to not send this case
+ rel = TYPESTRING_OTHER;
+ }
+
+ if (rel != null) {
+ final Object type = relToTypeMap.get(rel.toLowerCase());
+ if (type == null) {
+ throw new XmlPullParserException("unknown rel, " + rel);
+ }
+ element.setType(((Byte) type).byteValue());
+ }
+ element.setLabel(label);
+ element.setIsPrimary("true".equals(parser.getAttributeValue(null /* ns */, "primary")));
+ }
+
+ private static void handleOrganizationSubElement(Organization element, XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int depth = parser.getDepth();
+ while (true) {
+ String tag = XmlUtils.nextDirectChildTag(parser, depth);
+ if (tag == null) break;
+ if ("orgName".equals(tag)) {
+ element.setName(XmlUtils.extractChildText(parser));
+ } else if ("orgTitle".equals(tag)) {
+ element.setTitle(XmlUtils.extractChildText(parser));
+ }
+ }
+ }
+
+ /**
+ * Parse the ExtendedProperty. The parser is assumed to be at the beginning of the tag
+ * for the ExtendedProperty.
+ * @param extendedProperty the ExtendedProperty object to populate
+ */
+ private void parseExtendedProperty(ExtendedProperty extendedProperty)
+ throws IOException, XmlPullParserException {
+ XmlPullParser parser = getParser();
+ extendedProperty.setName(parser.getAttributeValue(null /* ns */, "name"));
+ extendedProperty.setValue(parser.getAttributeValue(null /* ns */, "value"));
+ extendedProperty.setXmlBlob(XmlUtils.extractFirstChildTextIgnoreRest(parser));
+ }
+}
diff --git a/src/com/google/wireless/gdata/contacts/parser/xml/XmlContactsGDataParserFactory.java b/src/com/google/wireless/gdata/contacts/parser/xml/XmlContactsGDataParserFactory.java
new file mode 100644
index 0000000..aedba39
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/parser/xml/XmlContactsGDataParserFactory.java
@@ -0,0 +1,125 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.contacts.parser.xml;
+
+import com.google.wireless.gdata.client.GDataParserFactory;
+import com.google.wireless.gdata.contacts.data.ContactEntry;
+import com.google.wireless.gdata.contacts.data.GroupEntry;
+import com.google.wireless.gdata.data.MediaEntry;
+import com.google.wireless.gdata.contacts.serializer.xml.XmlContactEntryGDataSerializer;
+import com.google.wireless.gdata.contacts.serializer.xml.XmlGroupEntryGDataSerializer;
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.parser.GDataParser;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.parser.xml.XmlParserFactory;
+import com.google.wireless.gdata.parser.xml.XmlMediaEntryGDataParser;
+import com.google.wireless.gdata.serializer.GDataSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.InputStream;
+
+/**
+ * GDataParserFactory that creates XML GDataParsers and GDataSerializers for
+ * Google Contacts.
+ */
+public class XmlContactsGDataParserFactory implements GDataParserFactory {
+
+ private final XmlParserFactory xmlFactory;
+
+ public XmlContactsGDataParserFactory(XmlParserFactory xmlFactory) {
+ this.xmlFactory = xmlFactory;
+ }
+
+ /**
+ * Returns a parser for a contacts group feed.
+ *
+ * @param is The input stream to be parsed.
+ * @return A parser for the stream.
+ * @throws com.google.wireless.gdata.parser.ParseException
+ */
+ public GDataParser createGroupEntryFeedParser(InputStream is) throws ParseException {
+ XmlPullParser xmlParser;
+ try {
+ xmlParser = xmlFactory.createParser();
+ } catch (XmlPullParserException xppe) {
+ throw new ParseException("Could not create XmlPullParser", xppe);
+ }
+ return new XmlGroupEntryGDataParser(is, xmlParser);
+ }
+
+ /**
+ * Returns a parser for a media entry feed.
+ *
+ * @param is The input stream to be parsed.
+ * @return A parser for the stream.
+ * @throws ParseException
+ */
+ public GDataParser createMediaEntryFeedParser(InputStream is) throws ParseException {
+ XmlPullParser xmlParser;
+ try {
+ xmlParser = xmlFactory.createParser();
+ } catch (XmlPullParserException xppe) {
+ throw new ParseException("Could not create XmlPullParser", xppe);
+ }
+ return new XmlMediaEntryGDataParser(is, xmlParser);
+ }
+
+ /*
+ * (non-javadoc)
+ *
+ * @see GDataParserFactory#createParser
+ */
+ public GDataParser createParser(InputStream is) throws ParseException {
+ XmlPullParser xmlParser;
+ try {
+ xmlParser = xmlFactory.createParser();
+ } catch (XmlPullParserException xppe) {
+ throw new ParseException("Could not create XmlPullParser", xppe);
+ }
+ return new XmlContactsGDataParser(is, xmlParser);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.wireless.gdata.client.GDataParserFactory#createParser(
+ * int, java.io.InputStream)
+ */
+ public GDataParser createParser(Class entryClass, InputStream is)
+ throws ParseException {
+ if (entryClass == ContactEntry.class) {
+ return createParser(is);
+ }
+ if (entryClass == GroupEntry.class) {
+ return createGroupEntryFeedParser(is);
+ }
+ if (entryClass == MediaEntry.class) {
+ return createMediaEntryFeedParser(is);
+ }
+ throw new IllegalArgumentException("unexpected feed type, " + entryClass.getName());
+ }
+
+ /**
+ * Creates a new {@link GDataSerializer} for the provided entry. The entry
+ * <strong>must</strong> be an instance of {@link ContactEntry} or {@link GroupEntry}.
+ *
+ * @param entry The {@link ContactEntry} that should be serialized.
+ * @return The {@link GDataSerializer} that will serialize this entry.
+ * @throws IllegalArgumentException Thrown if entry is not a
+ * {@link ContactEntry} or {@link GroupEntry}.
+ * @see com.google.wireless.gdata.client.GDataParserFactory#createSerializer
+ */
+ public GDataSerializer createSerializer(Entry entry) {
+ if (entry instanceof ContactEntry) {
+ ContactEntry contactEntry = (ContactEntry) entry;
+ return new XmlContactEntryGDataSerializer(xmlFactory, contactEntry);
+ }
+ if (entry instanceof GroupEntry) {
+ GroupEntry groupEntry = (GroupEntry) entry;
+ return new XmlGroupEntryGDataSerializer(xmlFactory, groupEntry);
+ }
+ throw new IllegalArgumentException("unexpected entry type, " + entry.getClass().toString());
+ }
+}
diff --git a/src/com/google/wireless/gdata/contacts/parser/xml/XmlGroupEntryGDataParser.java b/src/com/google/wireless/gdata/contacts/parser/xml/XmlGroupEntryGDataParser.java
new file mode 100644
index 0000000..ccc50bb
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/parser/xml/XmlGroupEntryGDataParser.java
@@ -0,0 +1,60 @@
+package com.google.wireless.gdata.contacts.parser.xml;
+
+import com.google.wireless.gdata.contacts.data.GroupEntry;
+import com.google.wireless.gdata.contacts.data.GroupsFeed;
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.Feed;
+import com.google.wireless.gdata.data.StringUtils;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.parser.xml.XmlGDataParser;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.InputStream;
+
+/**
+ * GDataParser for a contact groups feed.
+ */
+public class XmlGroupEntryGDataParser extends XmlGDataParser {
+ /**
+ * Creates a new XmlGroupEntryGDataParser.
+ * @param is The InputStream that should be parsed.
+ * @param parser the XmlPullParser to use for the xml parsing
+ * @throws ParseException Thrown if a parser cannot be created.
+ */
+ public XmlGroupEntryGDataParser(InputStream is, XmlPullParser parser) throws ParseException {
+ super(is, parser);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.wireless.gdata.parser.xml.XmlGDataParser#createFeed()
+ */
+ protected Feed createFeed() {
+ return new GroupsFeed();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.wireless.gdata.parser.xml.XmlGDataParser#createEntry()
+ */
+ protected Entry createEntry() {
+ return new GroupEntry();
+ }
+
+ protected void handleExtraElementInEntry(Entry entry) {
+ XmlPullParser parser = getParser();
+
+ if (!(entry instanceof GroupEntry)) {
+ throw new IllegalArgumentException("Expected GroupEntry!");
+ }
+ GroupEntry groupEntry = (GroupEntry) entry;
+ String name = parser.getName();
+ if ("systemGroup".equals(name)) {
+ String systemGroup = parser.getAttributeValue(null /* ns */, "id");
+ // if the systemGroup is the empty string, convert it to a null
+ if (StringUtils.isEmpty(systemGroup)) systemGroup = null;
+ groupEntry.setSystemGroup(systemGroup);
+ }
+ }
+}
diff --git a/src/com/google/wireless/gdata/contacts/parser/xml/package.html b/src/com/google/wireless/gdata/contacts/parser/xml/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/parser/xml/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/contacts/serializer/package.html b/src/com/google/wireless/gdata/contacts/serializer/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/serializer/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/contacts/serializer/xml/XmlContactEntryGDataSerializer.java b/src/com/google/wireless/gdata/contacts/serializer/xml/XmlContactEntryGDataSerializer.java
new file mode 100644
index 0000000..670e24b
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/serializer/xml/XmlContactEntryGDataSerializer.java
@@ -0,0 +1,243 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.contacts.serializer.xml;
+
+import com.google.wireless.gdata.contacts.data.ContactEntry;
+import com.google.wireless.gdata.contacts.data.ContactsElement;
+import com.google.wireless.gdata.contacts.data.EmailAddress;
+import com.google.wireless.gdata.contacts.data.ImAddress;
+import com.google.wireless.gdata.contacts.data.Organization;
+import com.google.wireless.gdata.contacts.data.PhoneNumber;
+import com.google.wireless.gdata.contacts.data.PostalAddress;
+import com.google.wireless.gdata.contacts.data.GroupMembershipInfo;
+import com.google.wireless.gdata.contacts.parser.xml.XmlContactsGDataParser;
+import com.google.wireless.gdata.data.StringUtils;
+import com.google.wireless.gdata.data.ExtendedProperty;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.parser.xml.XmlGDataParser;
+import com.google.wireless.gdata.parser.xml.XmlParserFactory;
+import com.google.wireless.gdata.serializer.xml.XmlEntryGDataSerializer;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+/**
+ * Serializes Google Contact entries into the Atom XML format.
+ */
+public class XmlContactEntryGDataSerializer extends XmlEntryGDataSerializer {
+
+ public XmlContactEntryGDataSerializer(XmlParserFactory factory, ContactEntry entry) {
+ super(factory, entry);
+ }
+
+ @Override
+ protected void declareExtraEntryNamespaces(XmlSerializer serializer) throws IOException {
+ super.declareExtraEntryNamespaces(serializer);
+ serializer.setPrefix(XmlContactsGDataParser.NAMESPACE_CONTACTS,
+ XmlContactsGDataParser.NAMESPACE_CONTACTS_URI);
+ }
+
+ protected ContactEntry getContactEntry() {
+ return (ContactEntry) getEntry();
+ }
+
+ /* (non-Javadoc)
+ * @see XmlEntryGDataSerializer#serializeExtraEntryContents
+ */
+ protected void serializeExtraEntryContents(XmlSerializer serializer, int format)
+ throws ParseException, IOException {
+ ContactEntry entry = getContactEntry();
+ entry.validate();
+
+ serializeLink(serializer, XmlContactsGDataParser.LINK_REL_EDIT_PHOTO,
+ entry.getLinkEditPhotoHref(), entry.getLinkEditPhotoType());
+ serializeLink(serializer, XmlContactsGDataParser.LINK_REL_PHOTO,
+ entry.getLinkPhotoHref(), entry.getLinkPhotoType());
+
+ // 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());
+ }
+
+ Enumeration eachIm = entry.getImAddresses().elements();
+ while (eachIm.hasMoreElements()) {
+ serialize(serializer, (ImAddress) eachIm.nextElement());
+ }
+
+ Enumeration eachPhone = entry.getPhoneNumbers().elements();
+ while (eachPhone.hasMoreElements()) {
+ serialize(serializer, (PhoneNumber) eachPhone.nextElement());
+ }
+
+ Enumeration eachAddress = entry.getPostalAddresses().elements();
+ while (eachAddress.hasMoreElements()) {
+ serialize(serializer, (PostalAddress) eachAddress.nextElement());
+ }
+
+ Enumeration eachOrganization = entry.getOrganizations().elements();
+ while (eachOrganization.hasMoreElements()) {
+ serialize(serializer, (Organization) eachOrganization.nextElement());
+ }
+
+ Enumeration eachExtendedProperty = entry.getExtendedProperties().elements();
+ while (eachExtendedProperty.hasMoreElements()) {
+ serialize(serializer, (ExtendedProperty) eachExtendedProperty.nextElement());
+ }
+
+ Enumeration eachGroup = entry.getGroups().elements();
+ while (eachGroup.hasMoreElements()) {
+ serialize(serializer, (GroupMembershipInfo) eachGroup.nextElement());
+ }
+ }
+
+ private static void serialize(XmlSerializer serializer, EmailAddress email)
+ throws IOException, ParseException {
+ if (StringUtils.isEmptyOrWhitespace(email.getAddress())) return;
+ serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "email");
+ serializeContactsElement(serializer, email, XmlContactsGDataParser.TYPE_TO_REL_EMAIL);
+ serializer.attribute(null /* ns */, "address", email.getAddress());
+ serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "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");
+ serializeContactsElement(serializer, im, XmlContactsGDataParser.TYPE_TO_REL_IM);
+ serializer.attribute(null /* ns */, "address", im.getAddress());
+
+ String protocolString;
+ switch (im.getProtocolPredefined()) {
+ case ImAddress.PROTOCOL_NONE:
+ // don't include the attribute if no protocol was specified
+ break;
+
+ case ImAddress.PROTOCOL_CUSTOM:
+ protocolString = im.getProtocolCustom();
+ if (protocolString == null) {
+ throw new IllegalArgumentException(
+ "the protocol is custom, but the custom string is null");
+ }
+ serializer.attribute(null /* ns */, "protocol", protocolString);
+ break;
+
+ default:
+ protocolString = (String)XmlContactsGDataParser.IM_PROTOCOL_TYPE_TO_STRING_MAP.get(
+ new Byte(im.getProtocolPredefined()));
+ serializer.attribute(null /* ns */, "protocol", protocolString);
+ break;
+ }
+
+ serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "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");
+ serializeContactsElement(serializer, phone, XmlContactsGDataParser.TYPE_TO_REL_PHONE);
+ serializer.text(phone.getPhoneNumber());
+ serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "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");
+ 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");
+ }
+
+ 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");
+ }
+
+ private static void serialize(XmlSerializer serializer, PostalAddress addr)
+ throws IOException, ParseException {
+ if (StringUtils.isEmptyOrWhitespace(addr.getValue())) return;
+ serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "postalAddress");
+ serializeContactsElement(serializer, addr, XmlContactsGDataParser.TYPE_TO_REL_POSTAL);
+ final String addressValue = addr.getValue();
+ if (addressValue != null) serializer.text(addressValue);
+ serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "postalAddress");
+ }
+
+ 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())));
+ }
+ if (element.isPrimary()) {
+ serializer.attribute(null /* ns */, "primary", "true");
+ }
+ }
+
+ private static void serialize(XmlSerializer serializer, GroupMembershipInfo groupMembershipInfo)
+ throws IOException, ParseException {
+ final String group = groupMembershipInfo.getGroup();
+ final boolean isDeleted = groupMembershipInfo.isDeleted();
+
+ if (StringUtils.isEmptyOrWhitespace(group)) {
+ 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");
+ }
+
+ private static void serialize(XmlSerializer serializer, ExtendedProperty extendedProperty)
+ throws IOException, ParseException {
+ final String name = extendedProperty.getName();
+ final String value = extendedProperty.getValue();
+ final String xmlBlob = extendedProperty.getXmlBlob();
+
+ serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, "extendedProperty");
+ if (!StringUtils.isEmpty(name)) {
+ serializer.attribute(null /* ns */, "name", name);
+ }
+ if (!StringUtils.isEmpty(value)) {
+ serializer.attribute(null /* ns */, "value", value);
+ }
+ if (!StringUtils.isEmpty(xmlBlob)) {
+ serializeBlob(serializer, xmlBlob);
+ }
+ serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "extendedProperty");
+ }
+
+ private static void serializeBlob(XmlSerializer serializer, String blob)
+ throws IOException, ParseException {
+ serializer.text(blob);
+ }
+}
diff --git a/src/com/google/wireless/gdata/contacts/serializer/xml/XmlGroupEntryGDataSerializer.java b/src/com/google/wireless/gdata/contacts/serializer/xml/XmlGroupEntryGDataSerializer.java
new file mode 100644
index 0000000..e636ecc
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/serializer/xml/XmlGroupEntryGDataSerializer.java
@@ -0,0 +1,53 @@
+package com.google.wireless.gdata.contacts.serializer.xml;
+
+import com.google.wireless.gdata.contacts.data.GroupEntry;
+import com.google.wireless.gdata.contacts.parser.xml.XmlContactsGDataParser;
+import com.google.wireless.gdata.parser.xml.XmlParserFactory;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.serializer.xml.XmlEntryGDataSerializer;
+import com.google.wireless.gdata.data.StringUtils;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+
+/**
+ * Serializes Google Contact Group entries into the Atom XML format.
+ */
+public class XmlGroupEntryGDataSerializer extends XmlEntryGDataSerializer {
+
+ public XmlGroupEntryGDataSerializer(XmlParserFactory factory, GroupEntry entry) {
+ super(factory, entry);
+ }
+
+ protected GroupEntry getGroupEntry() {
+ return (GroupEntry) getEntry();
+ }
+
+ @Override
+ protected void declareExtraEntryNamespaces(XmlSerializer serializer) throws IOException {
+ super.declareExtraEntryNamespaces(serializer);
+ serializer.setPrefix(XmlContactsGDataParser.NAMESPACE_CONTACTS,
+ XmlContactsGDataParser.NAMESPACE_CONTACTS_URI);
+ }
+
+ /* (non-Javadoc)
+ * @see XmlEntryGDataSerializer#serializeExtraEntryContents
+ */
+ protected void serializeExtraEntryContents(XmlSerializer serializer, int format)
+ throws ParseException, IOException {
+ GroupEntry entry = getGroupEntry();
+ entry.validate();
+
+ serializeSystemGroup(entry, serializer);
+ }
+
+ private void serializeSystemGroup(GroupEntry entry, XmlSerializer serializer) throws IOException {
+ final String systemGroup = entry.getSystemGroup();
+ if (!StringUtils.isEmpty(systemGroup)) {
+ serializer.startTag(null /* ns */, "systemGroup");
+ serializer.attribute(null /* ns */, "id", systemGroup);
+ serializer.endTag(null /* ns */, "systemGroup");
+ }
+ }
+}
diff --git a/src/com/google/wireless/gdata/contacts/serializer/xml/package.html b/src/com/google/wireless/gdata/contacts/serializer/xml/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/contacts/serializer/xml/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/data/Entry.java b/src/com/google/wireless/gdata/data/Entry.java
new file mode 100644
index 0000000..3971f6b
--- /dev/null
+++ b/src/com/google/wireless/gdata/data/Entry.java
@@ -0,0 +1,291 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.data;
+
+import com.google.wireless.gdata.parser.ParseException;
+
+/**
+ * Entry in a GData feed.
+ */
+// TODO: make this an interface?
+// allow for writing directly into data structures used by native PIM, etc.,
+// APIs.
+// TODO: comment that setId(), etc., only used for parsing code.
+public class Entry {
+ private String id = null;
+ private String title = null;
+ private String editUri = null;
+ private String htmlUri = null;
+ private String summary = null;
+ private String content = null;
+ private String author = null;
+ private String email = null;
+ private String category = null;
+ private String categoryScheme = null;
+ private String publicationDate = null;
+ private String updateDate = null;
+ private boolean deleted = false;
+
+ /**
+ * Creates a new empty entry.
+ */
+ public Entry() {
+ }
+
+ /**
+ * Clears all the values in this entry.
+ */
+ public void clear() {
+ id = null;
+ title = null;
+ editUri = null;
+ htmlUri = null;
+ summary = null;
+ content = null;
+ author = null;
+ email = null;
+ category = null;
+ categoryScheme = null;
+ publicationDate = null;
+ updateDate = null;
+ deleted = false;
+ }
+
+ /**
+ * @return the author
+ */
+ public String getAuthor() {
+ return author;
+ }
+
+ /**
+ * @param author the author to set
+ */
+ public void setAuthor(String author) {
+ this.author = author;
+ }
+
+ /**
+ * @return the category
+ */
+ public String getCategory() {
+ return category;
+ }
+
+ /**
+ * @param category the category to set
+ */
+ public void setCategory(String category) {
+ this.category = category;
+ }
+
+ /**
+ * @return the categoryScheme
+ */
+ public String getCategoryScheme() {
+ return categoryScheme;
+ }
+
+ /**
+ * @param categoryScheme the categoryScheme to set
+ */
+ public void setCategoryScheme(String categoryScheme) {
+ this.categoryScheme = categoryScheme;
+ }
+
+ /**
+ * @return the content
+ */
+ public String getContent() {
+ return content;
+ }
+
+ /**
+ * @param content the content to set
+ */
+ public void setContent(String content) {
+ this.content = content;
+ }
+
+ /**
+ * @return the editUri
+ */
+ public String getEditUri() {
+ return editUri;
+ }
+
+ /**
+ * @param editUri the editUri to set
+ */
+ public void setEditUri(String editUri) {
+ this.editUri = editUri;
+ }
+
+ /**
+ * @return The uri for the HTML version of this entry.
+ */
+ public String getHtmlUri() {
+ return htmlUri;
+ }
+
+ /**
+ * Set the uri for the HTML version of this entry.
+ * @param htmlUri The uri for the HTML version of this entry.
+ */
+ public void setHtmlUri(String htmlUri) {
+ this.htmlUri = htmlUri;
+ }
+
+ /**
+ * @return the id
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * @param id the id to set
+ */
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ /**
+ * @return the publicationDate
+ */
+ public String getPublicationDate() {
+ return publicationDate;
+ }
+
+ /**
+ * @param publicationDate the publicationDate to set
+ */
+ public void setPublicationDate(String publicationDate) {
+ this.publicationDate = publicationDate;
+ }
+
+ /**
+ * @return the summary
+ */
+ public String getSummary() {
+ return summary;
+ }
+
+ /**
+ * @param summary the summary to set
+ */
+ public void setSummary(String summary) {
+ this.summary = summary;
+ }
+
+ /**
+ * @return the title
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * @param title the title to set
+ */
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ /**
+ * @return the updateDate
+ */
+ public String getUpdateDate() {
+ return updateDate;
+ }
+
+ /**
+ * @param updateDate the updateDate to set
+ */
+ public void setUpdateDate(String updateDate) {
+ this.updateDate = updateDate;
+ }
+
+ /**
+ * @return true if this entry represents a tombstone
+ */
+ public boolean isDeleted() {
+ return deleted;
+ }
+
+ /**
+ * @param isDeleted true if the entry is deleted
+ */
+ public void setDeleted(boolean isDeleted) {
+ deleted = isDeleted;
+ }
+
+ /**
+ * Appends the name and value to this StringBuffer, if value is not null.
+ * Uses the format: "<NAME>: <VALUE>\n"
+ * @param sb The StringBuffer in which the name and value should be
+ * appended.
+ * @param name The name that should be appended.
+ * @param value The value that should be appended.
+ */
+ protected void appendIfNotNull(StringBuffer sb,
+ String name, String value) {
+ if (!StringUtils.isEmpty(value)) {
+ sb.append(name);
+ sb.append(": ");
+ sb.append(value);
+ sb.append("\n");
+ }
+ }
+
+ /**
+ * Helper method that creates the String representation of this Entry.
+ * Called by {@link #toString()}.
+ * Subclasses can add additional data to the StringBuffer.
+ * @param sb The StringBuffer that should be modified to add to the String
+ * representation of this Entry.
+ */
+ protected void toString(StringBuffer sb) {
+ appendIfNotNull(sb, "ID", id);
+ appendIfNotNull(sb, "TITLE", title);
+ appendIfNotNull(sb, "EDIT URI", editUri);
+ appendIfNotNull(sb, "HTML URI", htmlUri);
+ appendIfNotNull(sb, "SUMMARY", summary);
+ appendIfNotNull(sb, "CONTENT", content);
+ appendIfNotNull(sb, "AUTHOR", author);
+ appendIfNotNull(sb, "CATEGORY", category);
+ appendIfNotNull(sb, "CATEGORY SCHEME", categoryScheme);
+ appendIfNotNull(sb, "PUBLICATION DATE", publicationDate);
+ appendIfNotNull(sb, "UPDATE DATE", updateDate);
+ appendIfNotNull(sb, "DELETED", String.valueOf(deleted));
+ }
+
+ /**
+ * Creates a StringBuffer and calls {@link #toString(StringBuffer)}. The
+ * return value for this method is simply the result of calling
+ * {@link StringBuffer#toString()} on this StringBuffer. Mainly used for
+ * debugging.
+ */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ toString(sb);
+ return sb.toString();
+ }
+
+ /**
+ * @return the email
+ */
+ public String getEmail() {
+ return email;
+ }
+
+ /**
+ * @param email the email to set
+ */
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public void validate() throws ParseException {
+ }
+}
diff --git a/src/com/google/wireless/gdata/data/ExtendedProperty.java b/src/com/google/wireless/gdata/data/ExtendedProperty.java
new file mode 100644
index 0000000..92e2c2b
--- /dev/null
+++ b/src/com/google/wireless/gdata/data/ExtendedProperty.java
@@ -0,0 +1,53 @@
+package com.google.wireless.gdata.data;
+
+import com.google.wireless.gdata.parser.ParseException;
+
+/**
+ * The extendedProperty gdata type
+ */
+public class ExtendedProperty {
+ private String name;
+ private String value;
+ private String xmlBlob;
+
+ public String getXmlBlob() {
+ return xmlBlob;
+ }
+
+ public void setXmlBlob(String xmlBlob) {
+ this.xmlBlob = xmlBlob;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public void toString(StringBuffer sb) {
+ sb.append("ExtendedProperty");
+ if (name != null) sb.append(" name:").append(name);
+ if (value != null) sb.append(" value:").append(value);
+ if (xmlBlob != null) sb.append(" xmlBlob:").append(xmlBlob);
+ }
+
+ public void validate() throws ParseException {
+ if (name == null) {
+ throw new ParseException("name must not be null");
+ }
+
+ if ((value == null && xmlBlob == null) || (value != null && xmlBlob != null)) {
+ throw new ParseException("exactly one of value and xmlBlob must be present");
+ }
+ }
+}
diff --git a/src/com/google/wireless/gdata/data/Feed.java b/src/com/google/wireless/gdata/data/Feed.java
new file mode 100644
index 0000000..440e181
--- /dev/null
+++ b/src/com/google/wireless/gdata/data/Feed.java
@@ -0,0 +1,122 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.data;
+
+/**
+ * Class containing information about a GData feed. Note that this feed does
+ * not contain any of the entries in that feed -- the entries are yielded
+ * separately from this Feed.
+ */
+// TODO: add a createEntry method?
+// TODO: comment that setters are only used for parsing code.
+public class Feed {
+ private int totalResults;
+ private int startIndex;
+ private int itemsPerPage;
+ private String title;
+ private String id;
+ private String lastUpdated;
+ private String category;
+ private String categoryScheme;
+
+ /**
+ * Creates a new, empty feed.
+ */
+ public Feed() {
+ }
+
+ public int getTotalResults() {
+ return totalResults;
+ }
+
+ public void setTotalResults(int totalResults) {
+ this.totalResults = totalResults;
+ }
+
+ public int getStartIndex() {
+ return startIndex;
+ }
+
+ public void setStartIndex(int startIndex) {
+ this.startIndex = startIndex;
+ }
+
+ public int getItemsPerPage() {
+ return itemsPerPage;
+ }
+
+ public void setItemsPerPage(int itemsPerPage) {
+ this.itemsPerPage = itemsPerPage;
+ }
+
+ /**
+ * @return the category
+ */
+ public String getCategory() {
+ return category;
+ }
+
+ /**
+ * @param category the category to set
+ */
+ public void setCategory(String category) {
+ this.category = category;
+ }
+
+ /**
+ * @return the categoryScheme
+ */
+ public String getCategoryScheme() {
+ return categoryScheme;
+ }
+
+ /**
+ * @param categoryScheme the categoryScheme to set
+ */
+ public void setCategoryScheme(String categoryScheme) {
+ this.categoryScheme = categoryScheme;
+ }
+
+ /**
+ * @return the id
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * @param id the id to set
+ */
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ /**
+ * @return the lastUpdated
+ */
+ public String getLastUpdated() {
+ return lastUpdated;
+ }
+
+ /**
+ * @param lastUpdated the lastUpdated to set
+ */
+ public void setLastUpdated(String lastUpdated) {
+ this.lastUpdated = lastUpdated;
+ }
+
+ /**
+ * @return the title
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * @param title the title to set
+ */
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+}
diff --git a/src/com/google/wireless/gdata/data/MediaEntry.java b/src/com/google/wireless/gdata/data/MediaEntry.java
new file mode 100644
index 0000000..873bd43
--- /dev/null
+++ b/src/com/google/wireless/gdata/data/MediaEntry.java
@@ -0,0 +1,10 @@
+package com.google.wireless.gdata.data;
+
+/**
+ * Entry containing information about media entries
+ */
+public class MediaEntry extends Entry {
+ public MediaEntry() {
+ super();
+ }
+}
diff --git a/src/com/google/wireless/gdata/data/StringUtils.java b/src/com/google/wireless/gdata/data/StringUtils.java
new file mode 100644
index 0000000..6036ab0
--- /dev/null
+++ b/src/com/google/wireless/gdata/data/StringUtils.java
@@ -0,0 +1,54 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.data;
+
+/**
+ * Utility class for working with and manipulating Strings.
+ */
+public final class StringUtils {
+ // utility class
+ private StringUtils() {
+ }
+
+ /**
+ * Returns whether or not the String is empty. A String is considered to
+ * be empty if it is null or if it has a length of 0.
+ * @param string The String that should be examined.
+ * @return Whether or not the String is empty.
+ */
+ public static boolean isEmpty(String string) {
+ return ((string == null) || (string.length() == 0));
+ }
+
+ /**
+ * Returns {@code true} if the given string is null, empty, or comprises only
+ * whitespace characters, as defined by {@link Character#isWhitespace(char)}.
+ *
+ * @param string The String that should be examined.
+ * @return {@code true} if {@code string} is null, empty, or consists of
+ * whitespace characters only
+ */
+ public static boolean isEmptyOrWhitespace(String string) {
+ if (string == null) {
+ return true;
+ }
+ int length = string.length();
+ for (int i = 0; i < length; i++) {
+ if (!Character.isWhitespace(string.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static int parseInt(String string, int defaultValue) {
+ if (string != null) {
+ try {
+ return Integer.parseInt(string);
+ } catch (NumberFormatException nfe) {
+ // ignore
+ }
+ }
+ return defaultValue;
+ }
+}
diff --git a/src/com/google/wireless/gdata/data/XmlUtils.java b/src/com/google/wireless/gdata/data/XmlUtils.java
new file mode 100644
index 0000000..1c067d3
--- /dev/null
+++ b/src/com/google/wireless/gdata/data/XmlUtils.java
@@ -0,0 +1,133 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.data;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+
+/**
+ * Utility class for working with an XmlPullParser.
+ */
+public final class XmlUtils {
+ // utility class
+ private XmlUtils() {
+ }
+
+ /**
+ * Extracts the child text for the current element in the pull parser.
+ * @param parser The XmlPullParser parsing an XML document.
+ * @return The child text for the current element. May be null, if there
+ * is no child text.
+ * @throws XmlPullParserException Thrown if the child text could not be
+ * parsed.
+ * @throws IOException Thrown if the InputStream behind the parser cannot
+ * be read.
+ */
+ public static String extractChildText(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ // TODO: check that the current node is an element?
+ int eventType = parser.next();
+ if (eventType != XmlPullParser.TEXT) {
+ return null;
+ }
+ return parser.getText();
+ }
+
+ public static String extractFirstChildTextIgnoreRest(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int parentDepth = parser.getDepth();
+ int eventType = parser.next();
+ String child = null;
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ int depth = parser.getDepth();
+
+ if (eventType == XmlPullParser.TEXT) {
+ if (child == null) {
+ child = parser.getText();
+ }
+ } else if (eventType == XmlPullParser.END_TAG && depth == parentDepth) {
+ return child;
+ }
+ eventType = parser.next();
+ }
+ throw new XmlPullParserException("End of document reached; never saw expected end tag at "
+ + "depth " + parentDepth);
+ }
+
+ public static String nextDirectChildTag(XmlPullParser parser, int parentDepth)
+ throws XmlPullParserException, IOException {
+ int targetDepth = parentDepth + 1;
+ int eventType = parser.next();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ int depth = parser.getDepth();
+
+ if (eventType == XmlPullParser.START_TAG && depth == targetDepth) {
+ return parser.getName();
+ }
+
+ if (eventType == XmlPullParser.END_TAG && depth == parentDepth) {
+ return null;
+ }
+ eventType = parser.next();
+ }
+ throw new XmlPullParserException("End of document reached; never saw expected end tag at "
+ + "depth " + parentDepth);
+ }
+
+// public static void parseChildrenToSerializer(XmlPullParser parser, XmlSerializer serializer)
+// throws XmlPullParserException, IOException {
+// int parentDepth = parser.getDepth();
+// int eventType = parser.getEventType();
+// while (eventType != XmlPullParser.END_DOCUMENT) {
+// // TODO: call parser.nextToken(), so we get all entities, comments, whitespace, etc.?
+// // find out if this is necessary.
+// eventType = parser.next();
+// int depth = parser.getDepth();
+// String name;
+// String ns;
+// switch (eventType) {
+// case XmlPullParser.START_TAG:
+// name = parser.getName();
+// ns = parser.getNamespace();
+// // grab all of the namespace definitions between the previous depth and the
+// // current depth (e.g., what was just defined in the start tag).
+// int nstackBegin = parser.getNamespaceCount(depth - 1);
+// int nstackEnd = parser.getNamespaceCount(depth);
+// for (int i = nstackBegin; i < nstackEnd; ++i) {
+// serializer.setPrefix(parser.getNamespacePrefix(i),
+// parser.getNamespaceUri(i));
+// }
+// serializer.startTag(ns, name);
+//
+// int numAttrs = parser.getAttributeCount();
+// for (int i = 0; i < numAttrs; ++i) {
+// String attrNs = parser.getAttributeNamespace(i);
+// String attrName = parser.getAttributeName(i);
+// String attrValue = parser.getAttributeValue(i);
+// serializer.attribute(attrNs, attrName, attrValue);
+// }
+// break;
+// case XmlPullParser.END_TAG:
+// if (depth == parentDepth) {
+// // we're done.
+// return;
+// }
+// name = parser.getName();
+// ns = parser.getNamespace();
+// serializer.endTag(ns, name);
+// break;
+// case XmlPullParser.TEXT:
+// serializer.text(parser.getText());
+// break;
+// default:
+// // ignore the rest.
+// break;
+// }
+// }
+// throw new XmlPullParserException("End of document reached; never saw expected end tag "
+// + "at depth " + parentDepth);
+// }
+}
diff --git a/src/com/google/wireless/gdata/data/package.html b/src/com/google/wireless/gdata/data/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/data/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/package.html b/src/com/google/wireless/gdata/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/parser/GDataParser.java b/src/com/google/wireless/gdata/parser/GDataParser.java
new file mode 100644
index 0000000..71eb8be
--- /dev/null
+++ b/src/com/google/wireless/gdata/parser/GDataParser.java
@@ -0,0 +1,65 @@
+// Copyright 2008 The Android Open Source Project
+
+package com.google.wireless.gdata.parser;
+
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.Feed;
+
+import java.io.IOException;
+
+/**
+ * Interface for parsing GData feeds. Uses a &quot;pull&quot; model, where
+ * entries are not read or parsed until {@link #readNextEntry}
+ * is called.
+ */
+public interface GDataParser {
+
+ /**
+ * Starts parsing the feed, returning a {@link Feed} containing information
+ * about the feed. Note that the {@link Feed} does not contain any
+ * information about any entries, as the entries have not yet been parsed.
+ *
+ * @return The {@link Feed} containing information about the parsed feed.
+ * @throws ParseException Thrown if the feed cannot be parsed.
+ */
+ // TODO: rename to parseFeed? need to make the API clear.
+ Feed init() throws ParseException;
+
+ /**
+ * Parses a GData entry. You can either call {@link #init()} or
+ * {@link #parseStandaloneEntry()} for a given feed.
+ *
+ * @return The parsed entry.
+ * @throws ParseException Thrown if the entry could not be parsed.
+ */
+ Entry parseStandaloneEntry() throws ParseException, IOException;
+
+ /**
+ * Returns whether or not there is more data in the feed.
+ */
+ boolean hasMoreData();
+
+ /**
+ * Reads and parses the next entry in the feed. The {@link Entry} that
+ * should be filled is passed in -- if null, the entry will be created
+ * by the parser; if not null, the entry will be cleared and reused.
+ *
+ * @param entry The entry that should be filled. Should be null if this is
+ * the first call to this method. This entry is also returned as the return
+ * value.
+ *
+ * @return The {@link Entry} containing information about the parsed entry.
+ * If entry was not null, returns the same (reused) object as entry, filled
+ * with information about the entry that was just parsed. If the entry was
+ * null, returns a newly created entry (as appropriate for the type of
+ * feed being parsed).
+ * @throws ParseException Thrown if the entry cannot be parsed.
+ */
+ Entry readNextEntry(Entry entry) throws ParseException, IOException;
+
+ /**
+ * Cleans up any state in the parser. Should be called when caller is
+ * finished parsing a GData feed.
+ */
+ void close();
+}
diff --git a/src/com/google/wireless/gdata/parser/ParseException.java b/src/com/google/wireless/gdata/parser/ParseException.java
new file mode 100644
index 0000000..b9dc5e2
--- /dev/null
+++ b/src/com/google/wireless/gdata/parser/ParseException.java
@@ -0,0 +1,38 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.parser;
+
+import com.google.wireless.gdata.GDataException;
+
+/**
+ * Exception thrown if a GData feed cannot be parsed.
+ */
+public class ParseException extends GDataException {
+
+ /**
+ * Creates a new empty ParseException.
+ */
+ public ParseException() {
+ super();
+ }
+
+ /**
+ * Creates a new ParseException with the supplied message.
+ * @param message The message for this ParseException.
+ */
+ public ParseException(String message) {
+ super(message);
+ }
+
+ /**
+ * Creates a new ParseException with the supplied message and underlying
+ * cause.
+ *
+ * @param message The message for this ParseException.
+ * @param cause The underlying cause that was caught and wrapped by this
+ * ParseException.
+ */
+ public ParseException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/com/google/wireless/gdata/parser/package.html b/src/com/google/wireless/gdata/parser/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/parser/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/parser/xml/SimplePullParser.java b/src/com/google/wireless/gdata/parser/xml/SimplePullParser.java
new file mode 100644
index 0000000..d16aa5f
--- /dev/null
+++ b/src/com/google/wireless/gdata/parser/xml/SimplePullParser.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.wireless.gdata.parser.xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * This is an abstraction of a pull parser that provides several benefits:<ul>
+ * <li>it is easier to use robustly because it makes it trivial to handle unexpected tags (which
+ * might have children)</li>
+ * <li>it makes the handling of text (cdata) blocks more convenient</li>
+ * <li>it provides convenient methods for getting a mandatory attribute (and throwing an exception
+ * if it is missing) or an optional attribute (and using a default value if it is missing)
+ * </ul>
+ */
+public class SimplePullParser {
+ public static final String TEXT_TAG = "![CDATA[";
+
+ private final XmlPullParser mParser;
+ private String mCurrentStartTag;
+
+ /**
+ * Constructs a new SimplePullParser to parse the xml
+ * @param parser the underlying parser to use
+ */
+ public SimplePullParser(XmlPullParser parser) {
+ mParser = parser;
+ mCurrentStartTag = null;
+ }
+
+ /**
+ * Returns the tag of the next element whose depth is parentDepth plus one
+ * or null if there are no more such elements before the next start tag. When this returns,
+ * getDepth() and all methods relating to attributes will refer to the element whose tag is
+ * returned.
+ *
+ * @param parentDepth the depth of the parrent of the item to be returned
+ * @param textBuffer if null then text blocks will be ignored. If
+ * non-null then text blocks will be added to the builder and TEXT_TAG
+ * will be returned when one is found
+ * @return the next of the next child element's tag, TEXT_TAG if a text block is found, or null
+ * if there are no more child elements or DATA blocks
+ * @throws IOException propogated from the underlying parser
+ * @throws ParseException if there was an error parsing the xml.
+ */
+ public String nextTagOrText(int parentDepth, StringBuffer textBuffer)
+ throws IOException, ParseException {
+ while (true) {
+ int eventType = 0;
+ try {
+ eventType = mParser.next();
+ } catch (XmlPullParserException e) {
+ throw new ParseException(e);
+ }
+ int depth = mParser.getDepth();
+ mCurrentStartTag = null;
+
+ if (eventType == XmlPullParser.START_TAG && depth == parentDepth + 1) {
+ mCurrentStartTag = mParser.getName();
+ // TODO: this is an example of how to do logging of the XML
+// if (mLogTag != null && Log.isLoggable(mLogTag, Log.DEBUG)) {
+// StringBuilder sb = new StringBuilder();
+// for (int i = 0; i < depth; i++) sb.append(" ");
+// sb.append("<").append(mParser.getName());
+// int count = mParser.getAttributeCount();
+// for (int i = 0; i < count; i++) {
+// sb.append(" ");
+// sb.append(mParser.getAttributeName(i));
+// sb.append("=\"");
+// sb.append(mParser.getAttributeValue(i));
+// sb.append("\"");
+// }
+// sb.append(">");
+// Log.d(mLogTag, sb.toString());
+// }
+ return mParser.getName();
+ }
+
+ if (eventType == XmlPullParser.END_TAG && depth == parentDepth) {
+ // TODO: this is an example of how to do logging of the XML
+// if (mLogTag != null && Log.isLoggable(mLogTag, Log.DEBUG)) {
+// StringBuilder sb = new StringBuilder();
+// for (int i = 0; i < depth; i++) sb.append(" ");
+// sb.append("</>"); // Not quite valid xml but it gets the job done.
+// Log.d(mLogTag, sb.toString());
+// }
+ return null;
+ }
+
+ if (eventType == XmlPullParser.END_DOCUMENT && parentDepth == 0) {
+ return null;
+ }
+
+ if (eventType == XmlPullParser.TEXT && depth == parentDepth) {
+ if (textBuffer == null) {
+ continue;
+ }
+ String text = mParser.getText();
+ textBuffer.append(text);
+ return TEXT_TAG;
+ }
+ }
+ }
+
+ /**
+ * The same as nextTagOrText(int, StringBuilder) but ignores text blocks.
+ */
+ public String nextTag(int parentDepth) throws IOException, ParseException {
+ return nextTagOrText(parentDepth, null /* ignore text */);
+ }
+
+ /**
+ * Returns the depth of the current element. The depth is 0 before the first
+ * element has been returned, 1 after that, etc.
+ *
+ * @return the depth of the current element
+ */
+ public int getDepth() {
+ return mParser.getDepth();
+ }
+
+ /**
+ * Consumes the rest of the children, accumulating any text at this level into the builder.
+ *
+ * @param textBuffer
+ * @throws IOException propogated from the XmlPullParser
+ * @throws ParseException if there was an error parsing the xml.
+ */
+ public void readRemainingText(int parentDepth, StringBuffer textBuffer)
+ throws IOException, ParseException {
+ while (nextTagOrText(parentDepth, textBuffer) != null) {
+ }
+ }
+
+ /**
+ * Returns the number of attributes on the current element.
+ *
+ * @return the number of attributes on the current element
+ */
+ public int numAttributes() {
+ return mParser.getAttributeCount();
+ }
+
+ /**
+ * Returns the name of the nth attribute on the current element.
+ *
+ * @return the name of the nth attribute on the current element
+ */
+ public String getAttributeName(int i) {
+ return mParser.getAttributeName(i);
+ }
+
+ /**
+ * Returns the namespace of the nth attribute on the current element.
+ *
+ * @return the namespace of the nth attribute on the current element
+ */
+ public String getAttributeNamespace(int i) {
+ return mParser.getAttributeNamespace(i);
+ }
+
+ /**
+ * Returns the string value of the named attribute.
+ *
+ * @param namespace the namespace of the attribute
+ * @param name the name of the attribute
+ * @param defaultValue the value to return if the attribute is not specified
+ * @return the value of the attribute
+ */
+ public String getStringAttribute(
+ String namespace, String name, String defaultValue) {
+ String value = mParser.getAttributeValue(namespace, name);
+ if (null == value) return defaultValue;
+ return value;
+ }
+
+ /**
+ * Returns the string value of the named attribute. An exception will
+ * be thrown if the attribute is not present.
+ *
+ * @param namespace the namespace of the attribute
+ * @param name the name of the attribute @return the value of the attribute
+ * @throws ParseException thrown if the attribute is missing
+ */
+ public String getStringAttribute(String namespace, String name) throws ParseException {
+ String value = mParser.getAttributeValue(namespace, name);
+ if (null == value) {
+ throw new ParseException(
+ "missing '" + name + "' attribute on '" + mCurrentStartTag + "' element");
+ }
+ return value;
+ }
+
+ /**
+ * Returns the string value of the named attribute. An exception will
+ * be thrown if the attribute is not a valid integer.
+ *
+ * @param namespace the namespace of the attribute
+ * @param name the name of the attribute
+ * @param defaultValue the value to return if the attribute is not specified
+ * @return the value of the attribute
+ * @throws ParseException thrown if the attribute not a valid integer.
+ */
+ public int getIntAttribute(String namespace, String name, int defaultValue)
+ throws ParseException {
+ String value = mParser.getAttributeValue(namespace, name);
+ if (null == value) return defaultValue;
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ throw new ParseException("Cannot parse '" + value + "' as an integer");
+ }
+ }
+
+ /**
+ * Returns the string value of the named attribute. An exception will
+ * be thrown if the attribute is not present or is not a valid integer.
+ *
+ * @param namespace the namespace of the attribute
+ * @param name the name of the attribute @return the value of the attribute
+ * @throws ParseException thrown if the attribute is missing or not a valid integer.
+ */
+ public int getIntAttribute(String namespace, String name)
+ throws ParseException {
+ String value = getStringAttribute(namespace, name);
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ throw new ParseException("Cannot parse '" + value + "' as an integer");
+ }
+ }
+
+ /**
+ * Returns the string value of the named attribute. An exception will
+ * be thrown if the attribute is not a valid long.
+ *
+ * @param namespace the namespace of the attribute
+ * @param name the name of the attribute @return the value of the attribute
+ * @throws ParseException thrown if the attribute is not a valid long.
+ */
+ public long getLongAttribute(String namespace, String name, long defaultValue)
+ throws ParseException {
+ String value = mParser.getAttributeValue(namespace, name);
+ if (null == value) return defaultValue;
+ try {
+ return Long.parseLong(value);
+ } catch (NumberFormatException e) {
+ throw new ParseException("Cannot parse '" + value + "' as a long");
+ }
+ }
+
+ /**
+ * Returns the string value of the named attribute. An exception will
+ * be thrown if the attribute is not present or is not a valid long.
+ *
+ * @param namespace the namespace of the attribute
+ * @param name the name of the attribute @return the value of the attribute
+ * @throws ParseException thrown if the attribute is missing or not a valid long.
+ */
+ public long getLongAttribute(String namespace, String name)
+ throws ParseException {
+ String value = getStringAttribute(namespace, name);
+ try {
+ return Long.parseLong(value);
+ } catch (NumberFormatException e) {
+ throw new ParseException("Cannot parse '" + value + "' as a long");
+ }
+ }
+
+ public static final class ParseException extends Exception {
+ public ParseException(String message) {
+ super(message);
+ }
+
+ public ParseException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ParseException(Throwable cause) {
+ super(cause);
+ }
+ }
+}
diff --git a/src/com/google/wireless/gdata/parser/xml/XmlGDataParser.java b/src/com/google/wireless/gdata/parser/xml/XmlGDataParser.java
new file mode 100644
index 0000000..dbb0541
--- /dev/null
+++ b/src/com/google/wireless/gdata/parser/xml/XmlGDataParser.java
@@ -0,0 +1,530 @@
+// Copyright 2008 The Android Open Source Project
+
+package com.google.wireless.gdata.parser.xml;
+
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.Feed;
+import com.google.wireless.gdata.data.StringUtils;
+import com.google.wireless.gdata.data.XmlUtils;
+import com.google.wireless.gdata.parser.GDataParser;
+import com.google.wireless.gdata.parser.ParseException;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * {@link GDataParser} that uses an {@link XmlPullParser} to parse a GData feed.
+ */
+// NOTE: we do not perform any validity checks on the XML.
+public class XmlGDataParser implements GDataParser {
+
+ /** Namespace URI for Atom */
+ public static final String NAMESPACE_ATOM_URI =
+ "http://www.w3.org/2005/Atom";
+
+ public static final String NAMESPACE_OPENSEARCH = "openSearch";
+
+ public static final String NAMESPACE_OPENSEARCH_URI =
+ "http://a9.com/-/spec/opensearchrss/1.0/";
+
+ /** Namespace prefix for GData */
+ public static final String NAMESPACE_GD = "gd";
+
+ /** Namespace URI for GData */
+ public static final String NAMESPACE_GD_URI =
+ "http://schemas.google.com/g/2005";
+
+ private final InputStream is;
+ private final XmlPullParser parser;
+ private boolean isInBadState;
+
+ /**
+ * Creates a new XmlGDataParser for a feed in the provided InputStream.
+ * @param is The InputStream that should be parsed.
+ * @throws ParseException Thrown if an XmlPullParser could not be created
+ * or set around this InputStream.
+ */
+ public XmlGDataParser(InputStream is, XmlPullParser parser)
+ throws ParseException {
+ this.is = is;
+ this.parser = parser;
+ this.isInBadState = false;
+ if (this.is != null) {
+ try {
+ this.parser.setInput(is, null /* encoding */);
+ } catch (XmlPullParserException e) {
+ throw new ParseException("Could not create XmlGDataParser", e);
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.wireless.gdata.parser.GDataParser#init()
+ */
+ public final Feed init() throws ParseException {
+ int eventType;
+ try {
+ eventType = parser.getEventType();
+ } catch (XmlPullParserException e) {
+ throw new ParseException("Could not parse GData feed.", e);
+ }
+ if (eventType != XmlPullParser.START_DOCUMENT) {
+ throw new ParseException("Attempting to initialize parsing beyond "
+ + "the start of the document.");
+ }
+
+ try {
+ eventType = parser.next();
+ } catch (XmlPullParserException xppe) {
+ throw new ParseException("Could not read next event.", xppe);
+ } catch (IOException ioe) {
+ throw new ParseException("Could not read next event.", ioe);
+ }
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ switch (eventType) {
+ case XmlPullParser.START_TAG:
+ String name = parser.getName();
+ if ("feed".equals(name)) {
+ try {
+ return parseFeed();
+ } catch (XmlPullParserException xppe) {
+ throw new ParseException("Unable to parse <feed>.",
+ xppe);
+ } catch (IOException ioe) {
+ throw new ParseException("Unable to parse <feed>.",
+ ioe);
+ }
+ }
+ break;
+ default:
+ // ignore
+ break;
+ }
+
+ try {
+ eventType = parser.next();
+ } catch (XmlPullParserException xppe) {
+ throw new ParseException("Could not read next event.", xppe);
+ } catch (IOException ioe) {
+ throw new ParseException("Could not read next event." , ioe);
+ }
+ }
+ throw new ParseException("No <feed> found in document.");
+ }
+
+ /**
+ * Returns the {@link XmlPullParser} being used to parse this feed.
+ */
+ protected final XmlPullParser getParser() {
+ return parser;
+ }
+
+ /**
+ * Creates a new {@link Feed} that should be filled with information about
+ * the feed that will be parsed.
+ * @return The {@link Feed} that should be filled.
+ */
+ protected Feed createFeed() {
+ return new Feed();
+ }
+
+ /**
+ * Creates a new {@link Entry} that should be filled with information about
+ * the entry that will be parsed.
+ * @return The {@link Entry} that should be filled.
+ */
+ protected Entry createEntry() {
+ return new Entry();
+ }
+
+ /**
+ * Parses the feed (but not any entries).
+ *
+ * @return A new {@link Feed} containing information about the feed.
+ * @throws XmlPullParserException Thrown if the XML document cannot be
+ * parsed.
+ * @throws IOException Thrown if the {@link InputStream} behind the feed
+ * cannot be read.
+ */
+ private final Feed parseFeed() throws XmlPullParserException, IOException {
+ Feed feed = createFeed();
+ // parsing <feed>
+ // not interested in any attributes -- move onto the children.
+ int eventType = parser.next();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ switch (eventType) {
+ case XmlPullParser.START_TAG:
+ String name = parser.getName();
+ if ("totalResults".equals(name)) {
+ feed.setTotalResults(StringUtils.parseInt(
+ XmlUtils.extractChildText(parser), 0));
+ } else if ("startIndex".equals(name)) {
+ feed.setStartIndex(StringUtils.parseInt(
+ XmlUtils.extractChildText(parser), 0));
+ } else if ("itemsPerPage".equals(name)) {
+ feed.setItemsPerPage(StringUtils.parseInt(
+ XmlUtils.extractChildText(parser), 0));
+ } else if ("title".equals(name)) {
+ feed.setTitle(XmlUtils.extractChildText(parser));
+ } else if ("id".equals(name)) {
+ feed.setId(XmlUtils.extractChildText(parser));
+ } else if ("updated".equals(name)) {
+ feed.setLastUpdated(XmlUtils.extractChildText(parser));
+ } else if ("category".equals(name)) {
+ String category =
+ parser.getAttributeValue(null /* ns */, "term");
+ if (!StringUtils.isEmpty(category)) {
+ feed.setCategory(category);
+ }
+ String categoryScheme =
+ parser.getAttributeValue(null /* ns */, "scheme");
+ if (!StringUtils.isEmpty(categoryScheme)) {
+ feed.setCategoryScheme(categoryScheme);
+ }
+ } else if ("entry".equals(name)) {
+ // stop parsing here.
+ // TODO: pay attention to depth?
+ return feed;
+ } else {
+ handleExtraElementInFeed(feed);
+ }
+ break;
+ default:
+ break;
+ }
+ eventType = parser.next();
+ }
+ // if we get here, we have a feed with no entries.
+ return feed;
+ }
+
+ /**
+ * Hook that allows extra (service-specific) elements in a &lt;feed&gt; to
+ * be parsed.
+ * @param feed The {@link Feed} being filled.
+ */
+ protected void handleExtraElementInFeed(Feed feed)
+ throws XmlPullParserException, IOException {
+ // no-op in this class.
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.wireless.gdata.parser.GDataParser#hasMoreData()
+ */
+ public boolean hasMoreData() {
+ if (isInBadState) {
+ return false;
+ }
+ try {
+ int eventType = parser.getEventType();
+ return (eventType != XmlPullParser.END_DOCUMENT);
+ } catch (XmlPullParserException xppe) {
+ return false;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.wireless.gdata.parser.GDataParser#readNextEntry
+ */
+ public Entry readNextEntry(Entry entry) throws ParseException, IOException {
+ if (!hasMoreData()) {
+ throw new IllegalStateException("you shouldn't call this if hasMoreData() is false");
+ }
+
+ int eventType;
+ try {
+ eventType = parser.getEventType();
+ } catch (XmlPullParserException e) {
+ throw new ParseException("Could not parse entry.", e);
+ }
+
+ if (eventType != XmlPullParser.START_TAG) {
+ throw new ParseException("Expected event START_TAG: Actual event: "
+ + XmlPullParser.TYPES[eventType]);
+ }
+
+ String name = parser.getName();
+ if (!"entry".equals(name)) {
+ throw new ParseException("Expected <entry>: Actual element: "
+ + "<" + name + ">");
+ }
+
+ if (entry == null) {
+ entry = createEntry();
+ } else {
+ entry.clear();
+ }
+
+ try {
+ parser.next();
+ handleEntry(entry);
+ entry.validate();
+ } catch (ParseException xppe1) {
+ try {
+ if (hasMoreData()) skipToNextEntry();
+ } catch (XmlPullParserException xppe2) {
+ // squelch the error -- let the original one stand.
+ // set isInBadState to ensure that the next call to hasMoreData() will return false.
+ isInBadState = true;
+ }
+ throw new ParseException("Could not parse <entry>, " + entry, xppe1);
+ } catch (XmlPullParserException xppe1) {
+ try {
+ if (hasMoreData()) skipToNextEntry();
+ } catch (XmlPullParserException xppe2) {
+ // squelch the error -- let the original one stand.
+ // set isInBadState to ensure that the next call to hasMoreData() will return false.
+ isInBadState = true;
+ }
+ throw new ParseException("Could not parse <entry>, " + entry, xppe1);
+ }
+ return entry;
+ }
+
+ /**
+ * Parses a GData entry. You can either call {@link #init()} or
+ * {@link #parseStandaloneEntry()} for a given feed.
+ *
+ * @return The parsed entry.
+ * @throws ParseException Thrown if the entry could not be parsed.
+ */
+ public Entry parseStandaloneEntry() throws ParseException, IOException {
+ Entry entry = createEntry();
+
+ int eventType;
+ try {
+ eventType = parser.getEventType();
+ } catch (XmlPullParserException e) {
+ throw new ParseException("Could not parse GData entry.", e);
+ }
+ if (eventType != XmlPullParser.START_DOCUMENT) {
+ throw new ParseException("Attempting to initialize parsing beyond "
+ + "the start of the document.");
+ }
+
+ try {
+ eventType = parser.next();
+ } catch (XmlPullParserException xppe) {
+ throw new ParseException("Could not read next event.", xppe);
+ } catch (IOException ioe) {
+ throw new ParseException("Could not read next event.", ioe);
+ }
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ switch (eventType) {
+ case XmlPullParser.START_TAG:
+ String name = parser.getName();
+ if ("entry".equals(name)) {
+ try {
+ parser.next();
+ handleEntry(entry);
+ return entry;
+ } catch (XmlPullParserException xppe) {
+ throw new ParseException("Unable to parse <entry>.",
+ xppe);
+ } catch (IOException ioe) {
+ throw new ParseException("Unable to parse <entry>.",
+ ioe);
+ }
+ }
+ break;
+ default:
+ // ignore
+ break;
+ }
+
+ try {
+ eventType = parser.next();
+ } catch (XmlPullParserException xppe) {
+ throw new ParseException("Could not read next event.", xppe);
+ }
+ }
+ throw new ParseException("No <entry> found in document.");
+ }
+
+ /**
+ * Skips the rest of the current entry until the parser reaches the next entry, if any.
+ * Does nothing if the parser is already at the beginning of an entry.
+ */
+ protected void skipToNextEntry() throws IOException, XmlPullParserException {
+ if (!hasMoreData()) {
+ throw new IllegalStateException("you shouldn't call this if hasMoreData() is false");
+ }
+
+ int eventType = parser.getEventType();
+
+ // skip ahead until we reach an <entry> tag.
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ switch (eventType) {
+ case XmlPullParser.START_TAG:
+ if ("entry".equals(parser.getName())) {
+ return;
+ }
+ break;
+ }
+ eventType = parser.next();
+ }
+ }
+
+ /**
+ * Parses the current entry in the XML document. Assumes that the parser
+ * is currently pointing just after an &lt;entry&gt;.
+ *
+ * @param entry The entry that will be filled.
+ * @throws XmlPullParserException Thrown if the XML cannot be parsed.
+ * @throws IOException Thrown if the underlying inputstream cannot be read.
+ */
+ protected void handleEntry(Entry entry)
+ throws XmlPullParserException, IOException, ParseException {
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ switch (eventType) {
+ case XmlPullParser.START_TAG:
+ // TODO: make sure these elements are at the expected depth.
+ String name = parser.getName();
+ if ("entry".equals(name)) {
+ // stop parsing here.
+ return;
+ } else if ("id".equals(name)) {
+ entry.setId(XmlUtils.extractChildText(parser));
+ } else if ("title".equals(name)) {
+ entry.setTitle(XmlUtils.extractChildText(parser));
+ } else if ("link".equals(name)) {
+ String rel =
+ parser.getAttributeValue(null /* ns */, "rel");
+ String type =
+ parser.getAttributeValue(null /* ns */, "type");
+ String href =
+ parser.getAttributeValue(null /* ns */, "href");
+ if ("edit".equals(rel)) {
+ entry.setEditUri(href);
+ } else if (("alternate").equals(rel) && ("text/html".equals(type))) {
+ entry.setHtmlUri(href);
+ } else {
+ handleExtraLinkInEntry(rel,
+ type,
+ href,
+ entry);
+ }
+ } else if ("summary".equals(name)) {
+ entry.setSummary(XmlUtils.extractChildText(parser));
+ } else if ("content".equals(name)) {
+ // TODO: parse the type
+ entry.setContent(XmlUtils.extractChildText(parser));
+ } else if ("author".equals(name)) {
+ handleAuthor(entry);
+ } else if ("category".equals(name)) {
+ String category =
+ parser.getAttributeValue(null /* ns */, "term");
+ if (category != null && category.length() > 0) {
+ entry.setCategory(category);
+ }
+ String categoryScheme =
+ parser.getAttributeValue(null /* ns */, "scheme");
+ if (categoryScheme != null && category.length() > 0) {
+ entry.setCategoryScheme(categoryScheme);
+ }
+ } else if ("published".equals(name)) {
+ entry.setPublicationDate(
+ XmlUtils.extractChildText(parser));
+ } else if ("updated".equals(name)) {
+ entry.setUpdateDate(XmlUtils.extractChildText(parser));
+ } else if ("deleted".equals(name)) {
+ entry.setDeleted(true);
+ } else {
+ handleExtraElementInEntry(entry);
+ }
+ break;
+ default:
+ break;
+ }
+
+ eventType = parser.next();
+ }
+ }
+
+ private void handleAuthor(Entry entry)
+ throws XmlPullParserException, IOException {
+
+ int eventType = parser.getEventType();
+ String name = parser.getName();
+
+ if (eventType != XmlPullParser.START_TAG ||
+ (!"author".equals(parser.getName()))) {
+ // should not happen.
+ throw new
+ IllegalStateException("Expected <author>: Actual element: <"
+ + parser.getName() + ">");
+ }
+
+ eventType = parser.next();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ switch (eventType) {
+ case XmlPullParser.START_TAG:
+ name = parser.getName();
+ if ("name".equals(name)) {
+ String authorName = XmlUtils.extractChildText(parser);
+ entry.setAuthor(authorName);
+ } else if ("email".equals(name)) {
+ String email = XmlUtils.extractChildText(parser);
+ entry.setEmail(email);
+ }
+ break;
+ case XmlPullParser.END_TAG:
+ name = parser.getName();
+ if ("author".equals(name)) {
+ return;
+ }
+ default:
+ // ignore
+ }
+
+ eventType = parser.next();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.wireless.gdata.parser.GDataParser#close()
+ */
+ public void close() {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException ioe) {
+ // ignore
+ }
+ }
+ }
+
+ /**
+ * Hook that allows extra (service-specific) elements in an &lt;entry&gt;
+ * to be parsed.
+ * @param entry The {@link Entry} being filled.
+ */
+ protected void handleExtraElementInEntry(Entry entry)
+ throws XmlPullParserException, IOException, ParseException {
+ // no-op in this class.
+ }
+
+ /**
+ * Hook that allows extra (service-specific) &lt;link&gt;s in an entry to be
+ * parsed.
+ * @param rel The rel attribute value.
+ * @param type The type attribute value.
+ * @param href The href attribute value.
+ * @param entry The {@link Entry} being filled.
+ */
+ protected void handleExtraLinkInEntry(String rel,
+ String type,
+ String href,
+ Entry entry)
+ throws XmlPullParserException, IOException {
+ // no-op in this class.
+ }
+}
diff --git a/src/com/google/wireless/gdata/parser/xml/XmlMediaEntryGDataParser.java b/src/com/google/wireless/gdata/parser/xml/XmlMediaEntryGDataParser.java
new file mode 100644
index 0000000..dc4b1a7
--- /dev/null
+++ b/src/com/google/wireless/gdata/parser/xml/XmlMediaEntryGDataParser.java
@@ -0,0 +1,42 @@
+package com.google.wireless.gdata.parser.xml;
+
+import com.google.wireless.gdata.data.MediaEntry;
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.Feed;
+import com.google.wireless.gdata.parser.ParseException;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.InputStream;
+
+/**
+ * GDataParser for a MediaEntry. This must only be used to parse an entry, not a feed, since
+ * there is no such thing as a feed of media entries.
+ */
+public class XmlMediaEntryGDataParser extends XmlGDataParser {
+ /**
+ * Creates a new XmlMediaEntryGDataParser.
+ * @param is The InputStream that should be parsed.
+ * @param parser the XmlPullParser to use for the xml parsing
+ * @throws com.google.wireless.gdata.parser.ParseException Thrown if a parser cannot be created.
+ */
+ public XmlMediaEntryGDataParser(InputStream is, XmlPullParser parser) throws ParseException {
+ super(is, parser);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.wireless.gdata.parser.xml.XmlGDataParser#createFeed()
+ */
+ protected Feed createFeed() {
+ throw new UnsupportedOperationException("there is no such thing as a feed of media entries");
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.wireless.gdata.parser.xml.XmlGDataParser#createEntry()
+ */
+ protected Entry createEntry() {
+ return new MediaEntry();
+ }
+}
diff --git a/src/com/google/wireless/gdata/parser/xml/XmlParserFactory.java b/src/com/google/wireless/gdata/parser/xml/XmlParserFactory.java
new file mode 100644
index 0000000..0159618
--- /dev/null
+++ b/src/com/google/wireless/gdata/parser/xml/XmlParserFactory.java
@@ -0,0 +1,31 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.parser.xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+/**
+ * Factory for creating new {@link org.xmlpull.v1.XmlPullParser}s and
+ * {@link org.xmlpull.v1.XmlSerializer}s
+ */
+public interface XmlParserFactory {
+
+ /**
+ * Creates a new {@link XmlPullParser}.
+ *
+ * @return A new {@link XmlPullParser}.
+ * @throws XmlPullParserException Thrown if the parser could not be created.
+ */
+ XmlPullParser createParser() throws XmlPullParserException;
+
+ /**
+ * Creates a new {@link XmlSerializer}.
+ *
+ * @return A new {@link XmlSerializer}.
+ * @throws XmlPullParserException Thrown if the serializer could not be
+ * created.
+ */
+ XmlSerializer createSerializer() throws XmlPullParserException;
+}
diff --git a/src/com/google/wireless/gdata/parser/xml/package.html b/src/com/google/wireless/gdata/parser/xml/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/parser/xml/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/serializer/GDataSerializer.java b/src/com/google/wireless/gdata/serializer/GDataSerializer.java
new file mode 100644
index 0000000..971d624
--- /dev/null
+++ b/src/com/google/wireless/gdata/serializer/GDataSerializer.java
@@ -0,0 +1,56 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.serializer;
+
+import com.google.wireless.gdata.parser.ParseException;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Interface for serializing GData entries.
+ */
+public interface GDataSerializer {
+
+ // TODO: I hope the three formats does not bite us. Each serializer has
+ // to pay attention to what "mode" it is in when serializing.
+
+ /**
+ * Serialize all data in the entry. Used for debugging.
+ */
+ public static final int FORMAT_FULL = 0;
+
+ /**
+ * Serialize only the data necessary for creating a new entry.
+ */
+ public static final int FORMAT_CREATE = 1;
+
+ /**
+ * Serialize only the data necessary for updating an existing entry.
+ */
+ public static final int FORMAT_UPDATE = 2;
+
+ /**
+ * Returns the Content-Type for this serialization format.
+ * @return The Content-Type for this serialization format.
+ */
+ String getContentType();
+
+ /**
+ * Serializes a GData entry to the provided {@link OutputStream}, using the
+ * specified serialization format.
+ *
+ * @see #FORMAT_FULL
+ * @see #FORMAT_CREATE
+ * @see #FORMAT_UPDATE
+ *
+ * @param out The {@link OutputStream} to which the entry should be
+ * serialized.
+ * @param format The format of the serialized output.
+ * @throws IOException Thrown if there is an issue writing the serialized
+ * entry to the provided {@link OutputStream}.
+ * @throws ParseException Thrown if the entry cannot be serialized.
+ */
+ void serialize(OutputStream out, int format)
+ throws IOException, ParseException;
+}
diff --git a/src/com/google/wireless/gdata/serializer/package.html b/src/com/google/wireless/gdata/serializer/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/serializer/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/serializer/xml/XmlEntryGDataSerializer.java b/src/com/google/wireless/gdata/serializer/xml/XmlEntryGDataSerializer.java
new file mode 100644
index 0000000..023a5fa
--- /dev/null
+++ b/src/com/google/wireless/gdata/serializer/xml/XmlEntryGDataSerializer.java
@@ -0,0 +1,268 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.serializer.xml;
+
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.ExtendedProperty;
+import com.google.wireless.gdata.data.StringUtils;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.parser.xml.XmlGDataParser;
+import com.google.wireless.gdata.parser.xml.XmlParserFactory;
+import com.google.wireless.gdata.serializer.GDataSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Serializes GData entries to the Atom XML format.
+ */
+public class XmlEntryGDataSerializer implements GDataSerializer {
+
+ /** The XmlParserFactory that is used to create the XmlSerializer */
+ private final XmlParserFactory factory;
+
+ /** The entry being serialized. */
+ private final Entry entry;
+
+ /**
+ * Creates a new XmlEntryGDataSerializer that will serialize the provided
+ * entry.
+ *
+ * @param entry The entry that should be serialized.
+ */
+ public XmlEntryGDataSerializer(XmlParserFactory factory,
+ Entry entry) {
+ this.factory = factory;
+ this.entry = entry;
+ }
+
+ /**
+ * Returns the entry being serialized.
+ * @return The entry being serialized.
+ */
+ protected Entry getEntry() {
+ return entry;
+ }
+
+ /* (non-Javadoc)
+ * @see GDataSerializer#getContentType()
+ */
+ public String getContentType() {
+ return "application/atom+xml";
+ }
+
+ /* (non-Javadoc)
+ * @see GDataSerializer#serialize(java.io.OutputStream)
+ */
+ public void serialize(OutputStream out, int format)
+ throws IOException, ParseException {
+ XmlSerializer serializer = null;
+ try {
+ serializer = factory.createSerializer();
+ } catch (XmlPullParserException e) {
+ throw new ParseException("Unable to create XmlSerializer.", e);
+ }
+ // TODO: make the output compact
+
+ serializer.setOutput(out, "UTF-8");
+ serializer.startDocument("UTF-8", new Boolean(false));
+
+ declareEntryNamespaces(serializer);
+ serializer.startTag(XmlGDataParser.NAMESPACE_ATOM_URI, "entry");
+
+ serializeEntryContents(serializer, format);
+
+ serializer.endTag(XmlGDataParser.NAMESPACE_ATOM_URI, "entry");
+ serializer.endDocument();
+ serializer.flush();
+ }
+
+ private final void declareEntryNamespaces(XmlSerializer serializer)
+ throws IOException {
+ serializer.setPrefix("" /* default ns */,
+ XmlGDataParser.NAMESPACE_ATOM_URI);
+ serializer.setPrefix(XmlGDataParser.NAMESPACE_GD,
+ XmlGDataParser.NAMESPACE_GD_URI);
+ declareExtraEntryNamespaces(serializer);
+ }
+
+ protected void declareExtraEntryNamespaces(XmlSerializer serializer)
+ throws IOException {
+ // no-op in this class
+ }
+
+ /**
+ * @param serializer
+ * @throws IOException
+ */
+ private final void serializeEntryContents(XmlSerializer serializer,
+ int format)
+ throws ParseException, IOException {
+
+ if (format != FORMAT_CREATE) {
+ serializeId(serializer, entry.getId());
+ }
+
+ serializeTitle(serializer, entry.getTitle());
+
+ if (format != FORMAT_CREATE) {
+ serializeLink(serializer, "edit" /* rel */, entry.getEditUri(), null /* type */);
+ serializeLink(serializer, "alternate" /* rel */, entry.getHtmlUri(), "text/html" /* type */);
+ }
+
+ serializeSummary(serializer, entry.getSummary());
+
+ serializeContent(serializer, entry.getContent());
+
+ serializeAuthor(serializer, entry.getAuthor(), entry.getEmail());
+
+ serializeCategory(serializer,
+ entry.getCategory(), entry.getCategoryScheme());
+
+ if (format == FORMAT_FULL) {
+ serializePublicationDate(serializer,
+ entry.getPublicationDate());
+ }
+
+ if (format != FORMAT_CREATE) {
+ serializeUpdateDate(serializer,
+ entry.getUpdateDate());
+ }
+
+ serializeExtraEntryContents(serializer, format);
+ }
+
+ /**
+ * Hook for subclasses to serialize extra fields within the entry.
+ * @param serializer The XmlSerializer being used to serialize the entry.
+ * @param format The serialization format for the entry.
+ * @throws ParseException Thrown if the entry cannot be serialized.
+ * @throws IOException Thrown if the entry cannot be written to the
+ * underlying {@link OutputStream}.
+ */
+ protected void serializeExtraEntryContents(XmlSerializer serializer,
+ int format)
+ throws ParseException, IOException {
+ // no-op in this class.
+ }
+
+ // TODO: make these helper methods protected so sublcasses can use them?
+
+ private static void serializeId(XmlSerializer serializer,
+ String id) throws IOException {
+ if (StringUtils.isEmpty(id)) {
+ return;
+ }
+ serializer.startTag(null /* ns */, "id");
+ serializer.text(id);
+ serializer.endTag(null /* ns */, "id");
+ }
+
+ private static void serializeTitle(XmlSerializer serializer,
+ String title)
+ throws IOException {
+ if (StringUtils.isEmpty(title)) {
+ return;
+ }
+ serializer.startTag(null /* ns */, "title");
+ serializer.text(title);
+ serializer.endTag(null /* ns */, "title");
+ }
+
+ public static void serializeLink(XmlSerializer serializer, String rel, String href, String type)
+ throws IOException {
+ if (StringUtils.isEmpty(href)) {
+ return;
+ }
+ serializer.startTag(null /* ns */, "link");
+ serializer.attribute(null /* ns */, "rel", rel);
+ serializer.attribute(null /* ns */, "href", href);
+ if (!StringUtils.isEmpty(type)) serializer.attribute(null /* ns */, "type", type);
+ serializer.endTag(null /* ns */, "link");
+ }
+
+ private static void serializeSummary(XmlSerializer serializer,
+ String summary)
+ throws IOException {
+ if (StringUtils.isEmpty(summary)) {
+ return;
+ }
+ serializer.startTag(null /* ns */, "summary");
+ serializer.text(summary);
+ serializer.endTag(null /* ns */, "summary");
+ }
+
+ private static void serializeContent(XmlSerializer serializer,
+ String content)
+ throws IOException {
+ if (content == null) {
+ return;
+ }
+ serializer.startTag(null /* ns */, "content");
+ serializer.attribute(null /* ns */, "type", "text");
+ serializer.text(content);
+ serializer.endTag(null /* ns */, "content");
+ }
+
+ private static void serializeAuthor(XmlSerializer serializer,
+ String author,
+ String email)
+ throws IOException {
+ if (StringUtils.isEmpty(author) || StringUtils.isEmpty(email)) {
+ return;
+ }
+ serializer.startTag(null /* ns */, "author");
+ serializer.startTag(null /* ns */, "name");
+ serializer.text(author);
+ serializer.endTag(null /* ns */, "name");
+ serializer.startTag(null /* ns */, "email");
+ serializer.text(email);
+ serializer.endTag(null /* ns */, "email");
+ serializer.endTag(null /* ns */, "author");
+ }
+
+ private static void serializeCategory(XmlSerializer serializer,
+ String category,
+ String categoryScheme)
+ throws IOException {
+ if (StringUtils.isEmpty(category) &&
+ StringUtils.isEmpty(categoryScheme)) {
+ return;
+ }
+ serializer.startTag(null /* ns */, "category");
+ if (!StringUtils.isEmpty(category)) {
+ serializer.attribute(null /* ns */, "term", category);
+ }
+ if (!StringUtils.isEmpty(categoryScheme)) {
+ serializer.attribute(null /* ns */, "scheme", categoryScheme);
+ }
+ serializer.endTag(null /* ns */, "category");
+ }
+
+ private static void
+ serializePublicationDate(XmlSerializer serializer,
+ String publicationDate)
+ throws IOException {
+ if (StringUtils.isEmpty(publicationDate)) {
+ return;
+ }
+ serializer.startTag(null /* ns */, "published");
+ serializer.text(publicationDate);
+ serializer.endTag(null /* ns */, "published");
+ }
+
+ private static void
+ serializeUpdateDate(XmlSerializer serializer,
+ String updateDate)
+ throws IOException {
+ if (StringUtils.isEmpty(updateDate)) {
+ return;
+ }
+ serializer.startTag(null /* ns */, "updated");
+ serializer.text(updateDate);
+ serializer.endTag(null /* ns */, "updated");
+ }
+}
diff --git a/src/com/google/wireless/gdata/serializer/xml/package.html b/src/com/google/wireless/gdata/serializer/xml/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/serializer/xml/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/spreadsheets/client/SpreadsheetsClient.java b/src/com/google/wireless/gdata/spreadsheets/client/SpreadsheetsClient.java
new file mode 100755
index 0000000..388856d
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/client/SpreadsheetsClient.java
@@ -0,0 +1,236 @@
+// Copyright 2007 The Android Open Source Project
+package com.google.wireless.gdata.spreadsheets.client;
+
+import com.google.wireless.gdata.client.GDataClient;
+import com.google.wireless.gdata.client.GDataParserFactory;
+import com.google.wireless.gdata.client.GDataServiceClient;
+import com.google.wireless.gdata.client.HttpException;
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.StringUtils;
+import com.google.wireless.gdata.parser.GDataParser;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.serializer.GDataSerializer;
+import com.google.wireless.gdata.spreadsheets.data.CellEntry;
+import com.google.wireless.gdata.spreadsheets.data.ListEntry;
+import com.google.wireless.gdata.spreadsheets.data.SpreadsheetEntry;
+import com.google.wireless.gdata.spreadsheets.data.WorksheetEntry;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * GDataServiceClient for accessing Google Spreadsheets. This client can
+ * access and parse all of the Spreadsheets feed types: Spreadsheets feed,
+ * Worksheets feed, List feed, and Cells feed. Read operations are supported
+ * on all feed types, but only the List and Cells feeds support write
+ * operations. (This is a limitation of the protocol, not this API. Such write
+ * access may be added to the protocol in the future, requiring changes to
+ * this implementation.)
+ *
+ * Only 'private' visibility and 'full' projections are currently supported.
+ */
+public class SpreadsheetsClient extends GDataServiceClient {
+ /** The name of the service, dictated to be 'wise' by the protocol. */
+ private static final String SERVICE = "wise";
+
+ /** Standard base feed url for spreadsheets. */
+ public static final String SPREADSHEETS_BASE_FEED_URL =
+ "http://spreadsheets.google.com/feeds/spreadsheets/private/full";
+
+ /** Base feed url for spreadsheets. */
+ private final String baseFeedUrl;
+
+ /**
+ * Create a new SpreadsheetsClient.
+ *
+ * @param client The GDataClient that should be used to authenticate
+ * requests, retrieve feeds, etc.
+ * @param spreadsheetFactory The GDataParserFactory that should be used to obtain GDataParsers
+ * used by this client.
+ * @param baseFeedUrl The base URL for spreadsheets feeds.
+ */
+ public SpreadsheetsClient(GDataClient client,
+ GDataParserFactory spreadsheetFactory,
+ String baseFeedUrl) {
+ super(client, spreadsheetFactory);
+ this.baseFeedUrl = baseFeedUrl;
+ }
+
+ /**
+ * Create a new SpreadsheetsClient. Uses the standard base URL for spreadsheets feeds.
+ *
+ * @param client The GDataClient that should be used to authenticate
+ * requests, retrieve feeds, etc.
+ */
+ public SpreadsheetsClient(GDataClient client,
+ GDataParserFactory spreadsheetFactory) {
+ this(client, spreadsheetFactory, SPREADSHEETS_BASE_FEED_URL);
+ }
+
+ /* (non-Javadoc)
+ * @see GDataServiceClient#getServiceName
+ */
+ public String getServiceName() {
+ return SERVICE;
+ }
+
+ /**
+ * Returns a parser for the specified feed type.
+ *
+ * @param feedEntryClass the Class of entry type that will be parsed. This lets this
+ * method figure out which parser to create.
+ * @param feedUri the URI of the feed to be fetched and parsed
+ * @param authToken the current authToken to use for the request @return a parser for the indicated feed
+ * @throws HttpException if an http error is encountered
+ * @throws ParseException if the response from the server could not be
+ * parsed
+ */
+ private GDataParser getParserForTypedFeed(Class feedEntryClass, String feedUri,
+ String authToken) throws ParseException, IOException, HttpException {
+ GDataClient gDataClient = getGDataClient();
+ GDataParserFactory gDataParserFactory = getGDataParserFactory();
+
+ InputStream is = gDataClient.getFeedAsStream(feedUri, authToken);
+ return gDataParserFactory.createParser(feedEntryClass, is);
+ }
+
+ /* (non-javadoc)
+ * @see GDataServiceClient#createEntry
+ */
+ public Entry createEntry(String feedUri, String authToken, Entry entry)
+ throws ParseException, IOException, HttpException {
+
+ GDataParserFactory factory = getGDataParserFactory();
+ GDataSerializer serializer = factory.createSerializer(entry);
+
+ InputStream is = getGDataClient().createEntry(feedUri, authToken, serializer);
+ GDataParser parser = factory.createParser(entry.getClass(), is);
+ try {
+ return parser.parseStandaloneEntry();
+ } finally {
+ parser.close();
+ }
+ }
+
+ /**
+ * Returns a parser for a Cells-based feed.
+ *
+ * @param feedUri the URI of the feed to be fetched and parsed
+ * @param authToken the current authToken to use for the request
+ * @return a parser for the indicated feed
+ * @throws HttpException if an http error is encountered
+ * @throws ParseException if the response from the server could not be
+ * parsed
+ */
+ public GDataParser getParserForCellsFeed(String feedUri, String authToken)
+ throws ParseException, IOException, HttpException {
+ return getParserForTypedFeed(CellEntry.class, feedUri, authToken);
+ }
+
+ /**
+ * Fetches a GDataParser for the indicated feed. The parser can be used to
+ * access the contents of URI. WARNING: because we cannot reliably infer
+ * the feed type from the URI alone, this method assumes the default feed
+ * type! This is probably NOT what you want. Please use the
+ * getParserFor[Type]Feed methods.
+ *
+ * @param feedEntryClass
+ * @param feedUri the URI of the feed to be fetched and parsed
+ * @param authToken the current authToken to use for the request
+ * @return a parser for the indicated feed
+ * @throws HttpException if an http error is encountered
+ * @throws ParseException if the response from the server could not be
+ * parsed
+ */
+ public GDataParser getParserForFeed(Class feedEntryClass, String feedUri, String authToken)
+ throws ParseException, IOException, HttpException {
+ GDataClient gDataClient = getGDataClient();
+ GDataParserFactory gDataParserFactory = getGDataParserFactory();
+ InputStream is = gDataClient.getFeedAsStream(feedUri, authToken);
+ return gDataParserFactory.createParser(feedEntryClass, is);
+ }
+
+ /**
+ * Returns a parser for a List (row-based) feed.
+ *
+ * @param feedUri the URI of the feed to be fetched and parsed
+ * @param authToken the current authToken to use for the request
+ * @return a parser for the indicated feed
+ * @throws HttpException if an http error is encountered
+ * @throws ParseException if the response from the server could not be
+ * parsed
+ */
+ public GDataParser getParserForListFeed(String feedUri, String authToken)
+ throws ParseException, IOException, HttpException {
+ return getParserForTypedFeed(ListEntry.class, feedUri, authToken);
+ }
+
+ /**
+ * Returns a parser for a Spreadsheets meta-feed.
+ *
+ * @param feedUri the URI of the feed to be fetched and parsed
+ * @param authToken the current authToken to use for the request
+ * @return a parser for the indicated feed
+ * @throws HttpException if an http error is encountered
+ * @throws ParseException if the response from the server could not be
+ * parsed
+ */
+ public GDataParser getParserForSpreadsheetsFeed(String feedUri, String authToken)
+ throws ParseException, IOException, HttpException {
+ return getParserForTypedFeed(SpreadsheetEntry.class, feedUri, authToken);
+ }
+
+ /**
+ * Returns a parser for a Worksheets meta-feed.
+ *
+ * @param feedUri the URI of the feed to be fetched and parsed
+ * @param authToken the current authToken to use for the request
+ * @return a parser for the indicated feed
+ * @throws HttpException if an http error is encountered
+ * @throws ParseException if the response from the server could not be
+ * parsed
+ */
+ public GDataParser getParserForWorksheetsFeed(String feedUri, String authToken)
+ throws ParseException, IOException, HttpException {
+ return getParserForTypedFeed(WorksheetEntry.class, feedUri, authToken);
+ }
+
+ /**
+ * Updates an entry. The URI to be updated is taken from
+ * <code>entry</code>. Note that only entries in List and Cells feeds
+ * can be updated, so <code>entry</code> must be of the corresponding
+ * type; other types will result in an exception.
+ *
+ * @param entry the entry to be updated; must include its URI
+ * @param authToken the current authToken to be used for the operation
+ * @return An Entry containing the re-parsed version of the entry returned
+ * by the server in response to the update.
+ * @throws HttpException if an http error is encountered
+ * @throws ParseException if the server returned an error, if the server's
+ * response was unparseable (unlikely), or if <code>entry</code>
+ * is of a read-only type
+ * @throws IOException on network error
+ */
+ public Entry updateEntry(Entry entry, String authToken)
+ throws ParseException, IOException, HttpException {
+ GDataParserFactory factory = getGDataParserFactory();
+ GDataSerializer serializer = factory.createSerializer(entry);
+
+ String editUri = entry.getEditUri();
+ if (StringUtils.isEmpty(editUri)) {
+ throw new ParseException("No edit URI -- cannot update.");
+ }
+
+ InputStream is = getGDataClient().updateEntry(editUri, authToken, serializer);
+ GDataParser parser = factory.createParser(entry.getClass(), is);
+ try {
+ return parser.parseStandaloneEntry();
+ } finally {
+ parser.close();
+ }
+ }
+
+ public String getBaseFeedUrl() {
+ return baseFeedUrl;
+ }
+}
diff --git a/src/com/google/wireless/gdata/spreadsheets/client/package.html b/src/com/google/wireless/gdata/spreadsheets/client/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/client/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/spreadsheets/data/CellEntry.java b/src/com/google/wireless/gdata/spreadsheets/data/CellEntry.java
new file mode 100755
index 0000000..88fb6b6
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/data/CellEntry.java
@@ -0,0 +1,134 @@
+// Copyright 2007 The Android Open Source Project
+package com.google.wireless.gdata.spreadsheets.data;
+
+import com.google.wireless.gdata.data.Entry;
+
+/**
+ * Represents an entry in a GData Spreadsheets Cell-based feed.
+ */
+public class CellEntry extends Entry {
+ /** The spreadsheet column of the cell. */
+ private int col = -1;
+
+ /** The cell entry's inputValue attribute */
+ private String inputValue = null;
+
+ /** The cell entry's numericValue attribute */
+ private String numericValue = null;
+
+ /** The spreadsheet row of the cell */
+ private int row = -1;
+
+ /** The cell entry's text sub-element */
+ private String value = null;
+
+ /** Default constructor. */
+ public CellEntry() {
+ super();
+ }
+
+ /**
+ * Fetches the cell's spreadsheet column.
+ *
+ * @return the cell's spreadsheet column
+ */
+ public int getCol() {
+ return col;
+ }
+
+ /**
+ * Fetches the cell's inputValue attribute, which is the actual user input
+ * rather (such as a formula) than computed value of the cell.
+ *
+ * @return the cell's inputValue
+ */
+ public String getInputValue() {
+ return inputValue;
+ }
+
+ /**
+ * Fetches the cell's numericValue attribute, which is a decimal
+ * representation.
+ *
+ * @return the cell's numericValue
+ */
+ public String getNumericValue() {
+ return numericValue;
+ }
+
+ /**
+ * Fetches the cell's spreadsheet row.
+ *
+ * @return the cell's spreadsheet row
+ */
+ public int getRow() {
+ return row;
+ }
+
+ /**
+ * Fetches the cell's contents, after any computation. For example, if the
+ * cell actually contains a formula, this will return the formula's computed
+ * value.
+ *
+ * @return the computed value of the cell
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * Indicates whether the cell's contents are numeric.
+ *
+ * @return true if the contents are numeric, or false if not
+ */
+ public boolean hasNumericValue() {
+ return numericValue != null;
+ }
+
+ /**
+ * Sets the cell's spreadsheet column.
+ *
+ * @param col the new spreadsheet column of the cell
+ */
+ public void setCol(int col) {
+ this.col = col;
+ }
+
+ /**
+ * Sets the cell's actual contents (such as a formula, or a raw value.)
+ *
+ * @param inputValue the new inputValue of the cell
+ */
+ public void setInputValue(String inputValue) {
+ this.inputValue = inputValue;
+ }
+
+ /**
+ * Sets the cell's numeric value. This can be different from the actual
+ * value; for instance, the actual value may be a thousands-delimited pretty
+ * string, while the numeric value could be the raw decimal.
+ *
+ * @param numericValue the cell's new numericValue
+ */
+ public void setNumericValue(String numericValue) {
+ this.numericValue = numericValue;
+ }
+
+ /**
+ * Sets the cell's spreadsheet row.
+ *
+ * @param row the new spreadsheet row of the cell
+ */
+ public void setRow(int row) {
+ this.row = row;
+ }
+
+ /**
+ * Sets the cell's computed value.
+ *
+ * @param value the new value of the cell
+ */
+ public void setValue(String value) {
+ this.value = value;
+ }
+}
diff --git a/src/com/google/wireless/gdata/spreadsheets/data/CellFeed.java b/src/com/google/wireless/gdata/spreadsheets/data/CellFeed.java
new file mode 100755
index 0000000..9281e09
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/data/CellFeed.java
@@ -0,0 +1,35 @@
+// Copyright 2007 The Android Open Source Project
+package com.google.wireless.gdata.spreadsheets.data;
+
+import com.google.wireless.gdata.data.Feed;
+
+/**
+ * A feed handler for GData Spreadsheets cell-based feeds.
+ */
+public class CellFeed extends Feed {
+ private String editUri;
+
+ /** Default constructor. */
+ public CellFeed() {
+ super();
+ }
+
+ /**
+ * Fetches the URI to which edits (such as cell content updates) should
+ * go.
+ *
+ * @return the edit URI for this feed
+ */
+ public String getEditUri() {
+ return editUri;
+ }
+
+ /**
+ * Sets the URI to which edits (such as cell content updates) should go.
+ *
+ * @param editUri the new edit URI for this feed
+ */
+ public void setEditUri(String editUri) {
+ this.editUri = editUri;
+ }
+}
diff --git a/src/com/google/wireless/gdata/spreadsheets/data/ListEntry.java b/src/com/google/wireless/gdata/spreadsheets/data/ListEntry.java
new file mode 100755
index 0000000..c3e169f
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/data/ListEntry.java
@@ -0,0 +1,77 @@
+// Copyright 2007 The Android Open Source Project
+package com.google.wireless.gdata.spreadsheets.data;
+
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.StringUtils;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * Represents an entry in a GData Spreadsheets List feed.
+ */
+public class ListEntry extends Entry {
+ /** Map containing the values in the row. */
+ private Hashtable values = new Hashtable();
+
+ /** Caches the list of names, so they don't need to be recomputed. */
+ private Vector names = null;
+
+ /**
+ * Retrieves the column names present in this row.
+ *
+ * @return a Set of Strings, one per column where data exists
+ */
+ public Vector getNames() {
+ if (names != null) {
+ return names;
+ }
+ names = new Vector();
+ Enumeration e = values.keys();
+ while (e.hasMoreElements()) {
+ names.add(e.nextElement());
+ }
+ return names;
+ }
+
+ /**
+ * Fetches the value for a column. Equivalent to
+ * <code>getValue(name, null)</code>.
+ *
+ * @param name the name of the column whose row is to be fetched
+ * @return the value of the column, or null if the column is not present
+ */
+ public String getValue(String name) {
+ return getValue(name, null);
+ }
+
+ /**
+ * Fetches the value for a column.
+ *
+ * @param name the name of the column whose row is to be fetched
+ * @param defaultValue the value to return if the row has no value for the
+ * requested column; may be null
+ * @return the value of the column, or null if the column is not present
+ */
+ public String getValue(String name, String defaultValue) {
+ if (StringUtils.isEmpty(name)) {
+ return defaultValue;
+ }
+ String val = (String) values.get(name);
+ if (val == null) {
+ return defaultValue;
+ }
+ return val;
+ }
+
+ /**
+ * Sets the value of a column.
+ *
+ * @param name the name of the column
+ * @param value the value for the column
+ */
+ public void setValue(String name, String value) {
+ values.put(name, value == null ? "" : value);
+ }
+}
diff --git a/src/com/google/wireless/gdata/spreadsheets/data/ListFeed.java b/src/com/google/wireless/gdata/spreadsheets/data/ListFeed.java
new file mode 100755
index 0000000..d18bfa8
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/data/ListFeed.java
@@ -0,0 +1,35 @@
+// Copyright 2007 The Android Open Source Project
+package com.google.wireless.gdata.spreadsheets.data;
+
+import com.google.wireless.gdata.data.Feed;
+
+/**
+ * A feed handler for GData Spreadsheets List-based feeds.
+ */
+public class ListFeed extends Feed {
+ private String editUri;
+
+ /** Default constructor. */
+ public ListFeed() {
+ super();
+ }
+
+ /**
+ * Fetches the URI to which edits (such as cell content updates) should
+ * go.
+ *
+ * @return the edit URI for this feed
+ */
+ public String getEditUri() {
+ return editUri;
+ }
+
+ /**
+ * Sets the URI to which edits (such as cell content updates) should go.
+ *
+ * @param editUri the new edit URI for this feed
+ */
+ public void setEditUri(String editUri) {
+ this.editUri = editUri;
+ }
+}
diff --git a/src/com/google/wireless/gdata/spreadsheets/data/SpreadsheetEntry.java b/src/com/google/wireless/gdata/spreadsheets/data/SpreadsheetEntry.java
new file mode 100755
index 0000000..a10837b
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/data/SpreadsheetEntry.java
@@ -0,0 +1,38 @@
+// Copyright 2007 The Android Open Source Project
+package com.google.wireless.gdata.spreadsheets.data;
+
+import com.google.wireless.gdata.GDataException;
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.StringUtils;
+
+/**
+ * Represents an entry in a GData Spreadsheets meta-feed.
+ */
+public class SpreadsheetEntry extends Entry {
+ /** The URI of the worksheets meta-feed for this spreadsheet */
+ private String worksheetsUri = null;
+
+ /**
+ * Fetches the URI of the worksheets meta-feed (that is, list of worksheets)
+ * for this spreadsheet.
+ *
+ * @return the worksheets meta-feed URI
+ * @throws GDataException if the unique key is not set
+ */
+ public String getWorksheetFeedUri() throws GDataException {
+ if (StringUtils.isEmpty(worksheetsUri)) {
+ throw new GDataException("worksheet URI is not set");
+ }
+ return worksheetsUri;
+ }
+
+ /**
+ * Sets the URI of the worksheet meta-feed corresponding to this
+ * spreadsheet.
+ *
+ * @param href
+ */
+ public void setWorksheetFeedUri(String href) {
+ worksheetsUri = href;
+ }
+}
diff --git a/src/com/google/wireless/gdata/spreadsheets/data/SpreadsheetFeed.java b/src/com/google/wireless/gdata/spreadsheets/data/SpreadsheetFeed.java
new file mode 100755
index 0000000..bfab289
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/data/SpreadsheetFeed.java
@@ -0,0 +1,14 @@
+// Copyright 2007 The Android Open Source Project
+package com.google.wireless.gdata.spreadsheets.data;
+
+import com.google.wireless.gdata.data.Feed;
+
+/**
+ * Feed handler for a GData Spreadsheets meta-feed.
+ */
+public class SpreadsheetFeed extends Feed {
+ /** Default constructor. */
+ public SpreadsheetFeed() {
+ super();
+ }
+}
diff --git a/src/com/google/wireless/gdata/spreadsheets/data/WorksheetEntry.java b/src/com/google/wireless/gdata/spreadsheets/data/WorksheetEntry.java
new file mode 100644
index 0000000..2c00d66
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/data/WorksheetEntry.java
@@ -0,0 +1,105 @@
+// Copyright 2007 The Android Open Source Project
+package com.google.wireless.gdata.spreadsheets.data;
+
+import com.google.wireless.gdata.GDataException;
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.StringUtils;
+
+/**
+ * Represents an entry in a GData Worksheets meta-feed.
+ */
+public class WorksheetEntry extends Entry {
+ /** The URI to this entry's cells feed. */
+ private String cellsUri = null;
+
+ /** The number of columns in the worksheet. */
+ private int colCount = -1;
+
+ /** The URI to this entry's list feed. */
+ private String listUri = null;
+
+ /** The number of rows in the worksheet. */
+ private int rowCount = -1;
+
+ /**
+ * Fetches the URI of this entry's Cells feed.
+ *
+ * @return The URI of the entry's Cells feed.
+ */
+ public String getCellFeedUri() {
+ return cellsUri;
+ }
+
+ /**
+ * Fetches the number of columns in the worksheet.
+ *
+ * @return The number of columns.
+ */
+ public int getColCount() {
+ return colCount;
+ }
+
+ /**
+ * Fetches the URI of this entry's List feed.
+ *
+ * @return The URI of the entry's List feed.
+ * @throws GDataException If the URI is not set or is invalid.
+ */
+ public String getListFeedUri() {
+ return listUri;
+ }
+
+ /**
+ * Fetches the number of rows in the worksheet.
+ *
+ * @return The number of rows.
+ */
+ public int getRowCount() {
+ return rowCount;
+ }
+
+ /**
+ * Sets the URI of this entry's Cells feed.
+ *
+ * @param href The HREF attribute that should be the Cells feed URI.
+ */
+ public void setCellFeedUri(String href) {
+ cellsUri = href;
+ }
+
+ /**
+ * Sets the number of columns in the worksheet.
+ *
+ * @param colCount The new number of columns.
+ */
+ public void setColCount(int colCount) {
+ this.colCount = colCount;
+ }
+
+ /**
+ * Sets this entry's Atom ID.
+ *
+ * @param id The new ID value.
+ */
+ public void setId(String id) {
+ super.setId(id);
+ }
+
+ /**
+ * Sets the URI of this entry's List feed.
+ *
+ * @param href The HREF attribute that should be the List feed URI.
+ */
+ public void setListFeedUri(String href) {
+ listUri = href;
+ }
+
+ /**
+ * Sets the number of rows in the worksheet.
+ *
+ * @param rowCount The new number of rows.
+ */
+ public void setRowCount(int rowCount) {
+ this.rowCount = rowCount;
+ }
+}
diff --git a/src/com/google/wireless/gdata/spreadsheets/data/WorksheetFeed.java b/src/com/google/wireless/gdata/spreadsheets/data/WorksheetFeed.java
new file mode 100755
index 0000000..d8c469d
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/data/WorksheetFeed.java
@@ -0,0 +1,14 @@
+// Copyright 2007 The Android Open Source Project
+package com.google.wireless.gdata.spreadsheets.data;
+
+import com.google.wireless.gdata.data.Feed;
+
+/**
+ * Feed handler for GData Spreadsheets Worksheet meta-feed.
+ */
+public class WorksheetFeed extends Feed {
+ /** Default constructor. */
+ public WorksheetFeed() {
+ super();
+ }
+}
diff --git a/src/com/google/wireless/gdata/spreadsheets/data/package.html b/src/com/google/wireless/gdata/spreadsheets/data/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/data/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/spreadsheets/package.html b/src/com/google/wireless/gdata/spreadsheets/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/spreadsheets/parser/package.html b/src/com/google/wireless/gdata/spreadsheets/parser/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/parser/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlCellsGDataParser.java b/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlCellsGDataParser.java
new file mode 100755
index 0000000..b64f76a
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlCellsGDataParser.java
@@ -0,0 +1,126 @@
+// Copyright 2007 The Android Open Source Project
+package com.google.wireless.gdata.spreadsheets.parser.xml;
+
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.Feed;
+import com.google.wireless.gdata.data.StringUtils;
+import com.google.wireless.gdata.data.XmlUtils;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.parser.xml.XmlGDataParser;
+import com.google.wireless.gdata.spreadsheets.data.CellEntry;
+import com.google.wireless.gdata.spreadsheets.data.CellFeed;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Parser for non-Atom data in a GData Spreadsheets Cell-based feed.
+ */
+public class XmlCellsGDataParser extends XmlGDataParser {
+ /**
+ * The rel ID used by the server to identify the URLs for Cell POSTs
+ * (updates)
+ */
+ private static final String CELL_FEED_POST_REL =
+ "http://schemas.google.com/g/2005#post";
+
+ /**
+ * Creates a new XmlCellsGDataParser.
+ *
+ * @param is the stream from which to read the data
+ * @param xmlParser the XMLPullParser to use for parsing the raw XML
+ * @throws ParseException if the super-class throws one
+ */
+ public XmlCellsGDataParser(InputStream is, XmlPullParser xmlParser)
+ throws ParseException {
+ super(is, xmlParser);
+ }
+
+ /* (non-JavaDoc)
+ * Creates a new Entry that can handle the data parsed by this class.
+ */
+ protected Entry createEntry() {
+ return new CellEntry();
+ }
+
+ /* (non-JavaDoc)
+ * Creates a new Feed that can handle the data parsed by this class.
+ */
+ protected Feed createFeed() {
+ return new CellFeed();
+ }
+
+ /* (non-JavaDoc)
+ * Callback to handle non-Atom data present in an Atom entry tag.
+ */
+ protected void handleExtraElementInEntry(Entry entry)
+ throws XmlPullParserException, IOException {
+ XmlPullParser parser = getParser();
+ if (!(entry instanceof CellEntry)) {
+ throw new IllegalArgumentException("Expected CellEntry!");
+ }
+ CellEntry row = (CellEntry) entry;
+
+ String name = parser.getName();
+ // cells can only have row, col, inputValue, & numericValue attrs
+ if ("cell".equals(name)) {
+ int count = parser.getAttributeCount();
+ String attrName = null;
+ for (int i = 0; i < count; ++i) {
+ attrName = parser.getAttributeName(i);
+ if ("row".equals(attrName)) {
+ row.setRow(StringUtils.parseInt(parser
+ .getAttributeValue(i), 0));
+ } else if ("col".equals(attrName)) {
+ row.setCol(StringUtils.parseInt(parser
+ .getAttributeValue(i), 0));
+ } else if ("numericValue".equals(attrName)) {
+ row.setNumericValue(parser.getAttributeValue(i));
+ } else if ("inputValue".equals(attrName)) {
+ row.setInputValue(parser.getAttributeValue(i));
+ }
+ }
+
+ // also need the data stored in the child text node
+ row.setValue(XmlUtils.extractChildText(parser));
+ }
+ }
+
+ /* (non-JavaDoc)
+ * Callback to handle non-Atom data in the feed.
+ */
+ protected void handleExtraElementInFeed(Feed feed)
+ throws XmlPullParserException, IOException {
+ XmlPullParser parser = getParser();
+ if (!(feed instanceof CellFeed)) {
+ throw new IllegalArgumentException("Expected CellFeed!");
+ }
+ CellFeed cellFeed = (CellFeed) feed;
+
+ String name = parser.getName();
+ if (!"link".equals(name)) {
+ return;
+ }
+
+ int numAttrs = parser.getAttributeCount();
+ String rel = null;
+ String href = null;
+ String attrName = null;
+ for (int i = 0; i < numAttrs; ++i) {
+ attrName = parser.getAttributeName(i);
+ if ("rel".equals(attrName)) {
+ rel = parser.getAttributeValue(i);
+ } else if ("href".equals(attrName)) {
+ href = parser.getAttributeValue(i);
+ }
+ }
+ if (!(StringUtils.isEmpty(rel) || StringUtils.isEmpty(href))) {
+ if (CELL_FEED_POST_REL.equals(rel)) {
+ cellFeed.setEditUri(href);
+ }
+ }
+ }
+}
diff --git a/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlListGDataParser.java b/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlListGDataParser.java
new file mode 100755
index 0000000..b582ad9
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlListGDataParser.java
@@ -0,0 +1,109 @@
+// Copyright 2007 The Android Open Source Project
+package com.google.wireless.gdata.spreadsheets.parser.xml;
+
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.Feed;
+import com.google.wireless.gdata.data.StringUtils;
+import com.google.wireless.gdata.data.XmlUtils;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.parser.xml.XmlGDataParser;
+import com.google.wireless.gdata.spreadsheets.data.ListEntry;
+import com.google.wireless.gdata.spreadsheets.data.ListFeed;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Parser for non-Atom data in a GData Spreadsheets List-based feed.
+ */
+public class XmlListGDataParser extends XmlGDataParser {
+ /**
+ * The rel ID used by the server to identify the URLs for List POSTs
+ * (updates)
+ */
+ private static final String LIST_FEED_POST_REL =
+ "http://schemas.google.com/g/2005#post";
+
+ /**
+ * Creates a new XmlListGDataParser.
+ *
+ * @param is the stream from which to read the data
+ * @param xmlParser the XmlPullParser to use to parse the raw XML
+ * @throws ParseException if the super-class throws one
+ */
+ public XmlListGDataParser(InputStream is, XmlPullParser xmlParser)
+ throws ParseException {
+ super(is, xmlParser);
+ }
+
+ /* (non-JavaDoc)
+ * Creates a new Entry that can handle the data parsed by this class.
+ */
+ protected Entry createEntry() {
+ return new ListEntry();
+ }
+
+ /* (non-JavaDoc)
+ * Creates a new Feed that can handle the data parsed by this class.
+ */
+ protected Feed createFeed() {
+ return new ListFeed();
+ }
+
+ /* (non-JavaDoc)
+ * Callback to handle non-Atom data present in an Atom entry tag.
+ */
+ protected void handleExtraElementInEntry(Entry entry)
+ throws XmlPullParserException, IOException {
+ XmlPullParser parser = getParser();
+ if (!(entry instanceof ListEntry)) {
+ throw new IllegalArgumentException("Expected ListEntry!");
+ }
+ ListEntry row = (ListEntry) entry;
+
+ String name = parser.getName();
+ row.setValue(name, XmlUtils.extractChildText(parser));
+ }
+
+ /* (non-JavaDoc)
+ * Callback to handle non-Atom data in the feed.
+ */
+ protected void handleExtraElementInFeed(Feed feed)
+ throws XmlPullParserException, IOException {
+ XmlPullParser parser = getParser();
+ if (!(feed instanceof ListFeed)) {
+ throw new IllegalArgumentException("Expected ListFeed!");
+ }
+ ListFeed listFeed = (ListFeed) feed;
+
+ String name = parser.getName();
+ if (!"link".equals(name)) {
+ return;
+ }
+
+ // lists store column data in the gsx namespace:
+ // <gsx:columnheader>data</gsx:columnheader>
+ // The columnheader tag names are the scrubbed values of the first row.
+ // We extract them all and store them as keys in a Map.
+ int numAttrs = parser.getAttributeCount();
+ String rel = null;
+ String href = null;
+ String attrName = null;
+ for (int i = 0; i < numAttrs; ++i) {
+ attrName = parser.getAttributeName(i);
+ if ("rel".equals(attrName)) {
+ rel = parser.getAttributeValue(i);
+ } else if ("href".equals(attrName)) {
+ href = parser.getAttributeValue(i);
+ }
+ }
+ if (!(StringUtils.isEmpty(rel) || StringUtils.isEmpty(href))) {
+ if (LIST_FEED_POST_REL.equals(rel)) {
+ listFeed.setEditUri(href);
+ }
+ }
+ }
+}
diff --git a/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlSpreadsheetsGDataParser.java b/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlSpreadsheetsGDataParser.java
new file mode 100755
index 0000000..cb1094d
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlSpreadsheetsGDataParser.java
@@ -0,0 +1,68 @@
+// Copyright 2007 The Android Open Source Project
+package com.google.wireless.gdata.spreadsheets.parser.xml;
+
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.Feed;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.parser.xml.XmlGDataParser;
+import com.google.wireless.gdata.spreadsheets.data.SpreadsheetEntry;
+import com.google.wireless.gdata.spreadsheets.data.SpreadsheetFeed;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Parser helper for non-Atom data in a GData Spreadsheets meta-feed.
+ */
+public class XmlSpreadsheetsGDataParser extends XmlGDataParser {
+ /**
+ * The rel ID used by the server to identify the URLs for the worksheets
+ * feed
+ */
+ protected static final String WORKSHEET_FEED_REL =
+ "http://schemas.google.com/spreadsheets/2006#worksheetsfeed";
+
+ /**
+ * Creates a new XmlSpreadsheetsGDataParser.
+ *
+ * @param is the stream from which to read the data
+ * @param xmlParser the XmlPullParser to use to parse the raw XML
+ * @throws ParseException if the super-class throws one
+ */
+ public XmlSpreadsheetsGDataParser(InputStream is, XmlPullParser xmlParser)
+ throws ParseException {
+ super(is, xmlParser);
+ }
+
+ /* (non-JavaDoc)
+ * Creates a new Entry that can handle the data parsed by this class.
+ */
+ protected Entry createEntry() {
+ return new SpreadsheetEntry();
+ }
+
+ /* (non-JavaDoc)
+ * Creates a new Feed that can handle the data parsed by this class.
+ */
+ protected Feed createFeed() {
+ return new SpreadsheetFeed();
+ }
+
+ /* (non-JavaDoc)
+ * Callback to handle link elements that are not recognized as Atom links.
+ * Used to pick out the link tag in a Spreadsheet's entry that corresponds
+ * to that spreadsheet's worksheets meta-feed.
+ */
+ protected void handleExtraLinkInEntry(String rel, String type, String href,
+ Entry entry) throws XmlPullParserException, IOException {
+ super.handleExtraLinkInEntry(rel, type, href, entry);
+ if (WORKSHEET_FEED_REL.equals(rel)
+ && "application/atom+xml".equals(type)) {
+ SpreadsheetEntry sheet = (SpreadsheetEntry) entry;
+ sheet.setWorksheetFeedUri(href);
+ }
+ }
+}
diff --git a/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlSpreadsheetsGDataParserFactory.java b/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlSpreadsheetsGDataParserFactory.java
new file mode 100644
index 0000000..4feed62
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlSpreadsheetsGDataParserFactory.java
@@ -0,0 +1,99 @@
+// Copyright 2007 The Android Open Source Project
+package com.google.wireless.gdata.spreadsheets.parser.xml;
+
+import com.google.wireless.gdata.client.GDataParserFactory;
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.parser.GDataParser;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.parser.xml.XmlParserFactory;
+import com.google.wireless.gdata.serializer.GDataSerializer;
+import com.google.wireless.gdata.spreadsheets.data.CellEntry;
+import com.google.wireless.gdata.spreadsheets.data.ListEntry;
+import com.google.wireless.gdata.spreadsheets.data.SpreadsheetEntry;
+import com.google.wireless.gdata.spreadsheets.data.WorksheetEntry;
+import com.google.wireless.gdata.spreadsheets.serializer.xml.XmlCellEntryGDataSerializer;
+import com.google.wireless.gdata.spreadsheets.serializer.xml.XmlListEntryGDataSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.InputStream;
+
+/**
+ * A GDataParserFactory capable of handling Spreadsheets.
+ */
+public class XmlSpreadsheetsGDataParserFactory implements GDataParserFactory {
+ /*
+ * @see GDataParserFactory
+ */
+ public XmlSpreadsheetsGDataParserFactory(XmlParserFactory xmlFactory) {
+ this.xmlFactory = xmlFactory;
+ }
+
+ /** Intentionally private. */
+ private XmlSpreadsheetsGDataParserFactory() {
+ }
+
+ /*
+ * Creates a parser for the indicated feed, assuming the default feed type.
+ * The default type is specified on {@link SpreadsheetsClient#DEFAULT_FEED}.
+ *
+ * @param is The stream containing the feed to be parsed.
+ * @return A GDataParser capable of parsing the feed as the default type.
+ * @throws ParseException if the feed could not be parsed for any reason
+ */
+ public GDataParser createParser(InputStream is) throws ParseException {
+ // attempt a default
+ return createParser(SpreadsheetEntry.class, is);
+ }
+
+ /*
+ * Creates a parser of the indicated type for the indicated feed.
+ *
+ * @param feedType The type of the feed; must be one of the constants on
+ * {@link SpreadsheetsClient}.
+ * @return A parser capable of parsing the feed as the indicated type.
+ * @throws ParseException if the feed could not be parsed for any reason
+ */
+ public GDataParser createParser(Class entryClass, InputStream is)
+ throws ParseException {
+ try {
+ XmlPullParser xmlParser = xmlFactory.createParser();
+ if (entryClass == SpreadsheetEntry.class) {
+ return new XmlSpreadsheetsGDataParser(is, xmlParser);
+ } else if (entryClass == WorksheetEntry.class) {
+ return new XmlWorksheetsGDataParser(is, xmlParser);
+ } else if (entryClass == CellEntry.class) {
+ return new XmlCellsGDataParser(is, xmlParser);
+ } else if (entryClass == ListEntry.class) {
+ return new XmlListGDataParser(is, xmlParser);
+ } else {
+ throw new ParseException("Unrecognized feed requested.");
+ }
+ } catch (XmlPullParserException e) {
+ throw new ParseException("Failed to create parser", e);
+ }
+ }
+
+ /*
+ * Creates a serializer capable of handling the indicated entry.
+ *
+ * @param The Entry to be serialized to an XML string.
+ * @return A GDataSerializer capable of handling the indicated entry.
+ * @throws IllegalArgumentException if Entry is not a supported type (which
+ * currently includes only {@link ListEntry} and {@link CellEntry}.)
+ */
+ public GDataSerializer createSerializer(Entry entry) {
+ if (entry instanceof ListEntry) {
+ return new XmlListEntryGDataSerializer(xmlFactory, entry);
+ } else if (entry instanceof CellEntry) {
+ return new XmlCellEntryGDataSerializer(xmlFactory, entry);
+ } else {
+ throw new IllegalArgumentException(
+ "Expected a ListEntry or CellEntry");
+ }
+ }
+
+ /** The XmlParserFactory to use to actually process XML streams. */
+ private XmlParserFactory xmlFactory;
+}
diff --git a/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlWorksheetsGDataParser.java b/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlWorksheetsGDataParser.java
new file mode 100755
index 0000000..e9ceee5
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlWorksheetsGDataParser.java
@@ -0,0 +1,98 @@
+// Copyright 2007 The Android Open Source Project
+package com.google.wireless.gdata.spreadsheets.parser.xml;
+
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.Feed;
+import com.google.wireless.gdata.data.StringUtils;
+import com.google.wireless.gdata.data.XmlUtils;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.parser.xml.XmlGDataParser;
+import com.google.wireless.gdata.spreadsheets.data.WorksheetEntry;
+import com.google.wireless.gdata.spreadsheets.data.WorksheetFeed;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Parser helper for non-Atom data in a GData Spreadsheets Worksheets meta-feed.
+ */
+public class XmlWorksheetsGDataParser extends XmlGDataParser {
+ /**
+ * The rel ID used by the server to identify the cells feed for a worksheet
+ */
+ protected static final String CELLS_FEED_REL =
+ "http://schemas.google.com/spreadsheets/2006#cellsfeed";
+
+ /**
+ * The rel ID used by the server to identify the list feed for a worksheet
+ */
+ protected static final String LIST_FEED_REL =
+ "http://schemas.google.com/spreadsheets/2006#listfeed";
+
+ /**
+ * Creates a new XmlWorksheetsGDataParser.
+ *
+ * @param is the stream from which to read the data
+ * @param xmlParser the XmlPullParser to use to parse the raw XML
+ * @throws ParseException if the super-class throws one
+ */
+ public XmlWorksheetsGDataParser(InputStream is, XmlPullParser xmlParser)
+ throws ParseException {
+ super(is, xmlParser);
+ }
+
+ /* (non-JavaDoc)
+ * Creates a new Entry that can handle the data parsed by this class.
+ */
+ protected Entry createEntry() {
+ return new WorksheetEntry();
+ }
+
+ /* (non-JavaDoc)
+ * Creates a new Feed that can handle the data parsed by this class.
+ */
+ protected Feed createFeed() {
+ return new WorksheetFeed();
+ }
+
+ /* (non-JavaDoc)
+ * Callback to handle non-Atom data present in an Atom entry tag.
+ */
+ protected void handleExtraElementInEntry(Entry entry)
+ throws XmlPullParserException, IOException {
+ XmlPullParser parser = getParser();
+ if (!(entry instanceof WorksheetEntry)) {
+ throw new IllegalArgumentException("Expected WorksheetEntry!");
+ }
+ WorksheetEntry worksheet = (WorksheetEntry) entry;
+
+ // the only custom elements are rowCount and colCount
+ String name = parser.getName();
+ if ("rowCount".equals(name)) {
+ worksheet.setRowCount(StringUtils.parseInt(XmlUtils
+ .extractChildText(parser), 0));
+ } else if ("colCount".equals(name)) {
+ worksheet.setColCount(StringUtils.parseInt(XmlUtils
+ .extractChildText(parser), 0));
+ }
+ }
+
+ /* (non-JavaDoc)
+ * Callback to handle non-Atom links present in an Atom entry tag. Used to
+ * pick out a worksheet's cells and list feeds.
+ */
+ protected void handleExtraLinkInEntry(String rel, String type, String href,
+ Entry entry) throws XmlPullParserException, IOException {
+ if (LIST_FEED_REL.equals(rel) && "application/atom+xml".equals(type)) {
+ WorksheetEntry sheet = (WorksheetEntry) entry;
+ sheet.setListFeedUri(href);
+ } else if (CELLS_FEED_REL.equals(rel)
+ && "application/atom+xml".equals(type)) {
+ WorksheetEntry sheet = (WorksheetEntry) entry;
+ sheet.setCellFeedUri(href);
+ }
+ }
+}
diff --git a/src/com/google/wireless/gdata/spreadsheets/parser/xml/package.html b/src/com/google/wireless/gdata/spreadsheets/parser/xml/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/parser/xml/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/spreadsheets/serializer/package.html b/src/com/google/wireless/gdata/spreadsheets/serializer/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/serializer/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/spreadsheets/serializer/xml/XmlCellEntryGDataSerializer.java b/src/com/google/wireless/gdata/spreadsheets/serializer/xml/XmlCellEntryGDataSerializer.java
new file mode 100755
index 0000000..995af2c
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/serializer/xml/XmlCellEntryGDataSerializer.java
@@ -0,0 +1,83 @@
+package com.google.wireless.gdata.spreadsheets.serializer.xml;
+
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.StringUtils;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.parser.xml.XmlParserFactory;
+import com.google.wireless.gdata.serializer.xml.XmlEntryGDataSerializer;
+import com.google.wireless.gdata.spreadsheets.data.CellEntry;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+
+/**
+ * A serializer for handling GData Spreadsheets Cell entries.
+ */
+public class XmlCellEntryGDataSerializer extends XmlEntryGDataSerializer {
+ /** The namespace to use for the GData Cell attributes */
+ public static final String NAMESPACE_GS = "gs";
+
+ /** The URI of the GData Cell namespace */
+ public static final String NAMESPACE_GS_URI =
+ "http://schemas.google.com/spreadsheets/2006";
+
+ /**
+ * Creates a new XmlCellEntryGDataSerializer.
+ *
+ * @param entry the entry to be serialized
+ */
+ public XmlCellEntryGDataSerializer(XmlParserFactory xmlFactory,
+ Entry entry) {
+ super(xmlFactory, entry);
+ }
+
+ /**
+ * Sets up the GData Cell namespace.
+ *
+ * @param serializer the serializer to use
+ */
+ protected void declareExtraEntryNamespaces(XmlSerializer serializer)
+ throws IOException {
+ serializer.setPrefix(NAMESPACE_GS, NAMESPACE_GS_URI);
+ }
+
+ /*
+ * Handles the non-Atom data belonging to the GData Spreadsheets Cell
+ * namespace.
+ *
+ * @param serializer the XML serializer to use
+ * @param format unused
+ * @throws ParseException if the data could not be serialized
+ * @throws IOException on network error
+ */
+ protected void serializeExtraEntryContents(XmlSerializer serializer,
+ int format) throws ParseException, IOException {
+ CellEntry entry = (CellEntry) getEntry();
+ int row = entry.getRow();
+ int col = entry.getCol();
+ String value = entry.getValue();
+ String inputValue = entry.getInputValue();
+ if (row < 0 || col < 0) {
+ throw new ParseException("Negative row or column value");
+ }
+
+ // cells require row & col attrs, and allow inputValue and
+ // numericValue
+ serializer.startTag(NAMESPACE_GS_URI, "cell");
+ serializer.attribute(null /* ns */, "row", "" + row);
+ serializer.attribute(null /* ns */, "col", "" + col);
+ if (inputValue != null) {
+ serializer.attribute(null /* ns */, "inputValue", inputValue);
+ }
+ if (entry.hasNumericValue()) {
+ serializer.attribute(null /* ns */, "numericValue", entry
+ .getNumericValue());
+ }
+
+ // set the child text...
+ value = StringUtils.isEmpty(value) ? "" : value;
+ serializer.text(value);
+ serializer.endTag(NAMESPACE_GS_URI, "cell");
+ }
+}
diff --git a/src/com/google/wireless/gdata/spreadsheets/serializer/xml/XmlListEntryGDataSerializer.java b/src/com/google/wireless/gdata/spreadsheets/serializer/xml/XmlListEntryGDataSerializer.java
new file mode 100755
index 0000000..55cc59c
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/serializer/xml/XmlListEntryGDataSerializer.java
@@ -0,0 +1,67 @@
+package com.google.wireless.gdata.spreadsheets.serializer.xml;
+
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.parser.xml.XmlParserFactory;
+import com.google.wireless.gdata.serializer.xml.XmlEntryGDataSerializer;
+import com.google.wireless.gdata.spreadsheets.data.ListEntry;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Vector;
+
+/**
+ * A serializer for handling GData Spreadsheets List entries.
+ */
+public class XmlListEntryGDataSerializer extends XmlEntryGDataSerializer {
+ /** The prefix to use for the GData Spreadsheets list namespace */
+ public static final String NAMESPACE_GSX = "gsx";
+
+ /** The URI of the GData Spreadsheets list namespace */
+ public static final String NAMESPACE_GSX_URI =
+ "http://schemas.google.com/spreadsheets/2006/extended";
+
+ /**
+ * Creates a new XmlListEntryGDataSerializer.
+ *
+ * @param entry the entry to be serialized
+ */
+ public XmlListEntryGDataSerializer(XmlParserFactory xmlFactory, Entry entry) {
+ super(xmlFactory, entry);
+ }
+
+ /**
+ * Sets up the GData Spreadsheets list namespace.
+ *
+ * @param serializer the XML serializer to use
+ * @throws IOException on stream errors.
+ */
+ protected void declareExtraEntryNamespaces(XmlSerializer serializer)
+ throws IOException {
+ serializer.setPrefix(NAMESPACE_GSX, NAMESPACE_GSX_URI);
+ }
+
+ /* (non-JavaDoc)
+ * Handles the non-Atom data belonging to the GData Spreadsheets Cell
+ * namespace.
+ */
+ protected void serializeExtraEntryContents(XmlSerializer serializer,
+ int format) throws ParseException, IOException {
+ ListEntry entry = (ListEntry) getEntry();
+ Vector names = entry.getNames();
+ String name = null;
+ String value = null;
+ Iterator it = names.iterator();
+ while (it.hasNext()) {
+ name = (String) it.next();
+ value = entry.getValue(name);
+ if (value != null) {
+ serializer.startTag(NAMESPACE_GSX_URI, name);
+ serializer.text(value);
+ serializer.endTag(NAMESPACE_GSX_URI, name);
+ }
+ }
+ }
+}
diff --git a/src/com/google/wireless/gdata/spreadsheets/serializer/xml/package.html b/src/com/google/wireless/gdata/spreadsheets/serializer/xml/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/spreadsheets/serializer/xml/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/subscribedfeeds/client/SubscribedFeedsClient.java b/src/com/google/wireless/gdata/subscribedfeeds/client/SubscribedFeedsClient.java
new file mode 100644
index 0000000..ede852b
--- /dev/null
+++ b/src/com/google/wireless/gdata/subscribedfeeds/client/SubscribedFeedsClient.java
@@ -0,0 +1,36 @@
+// Copyright 2007 The Android Open Source Project
+
+package com.google.wireless.gdata.subscribedfeeds.client;
+
+import com.google.wireless.gdata.client.GDataClient;
+import com.google.wireless.gdata.client.GDataParserFactory;
+import com.google.wireless.gdata.client.GDataServiceClient;
+
+/**
+ * GDataServiceClient for accessing Subscribed Feeds. This client can access
+ * subscribed feeds for specific users. The parser this class uses handles
+ * the XML version of feeds.
+ */
+public class SubscribedFeedsClient extends GDataServiceClient {
+
+ /** Service value for contacts. This is only used for downloads; uploads
+ * are done using the service that corresponds to the subscribed feed. */
+ public static final String SERVICE = "mail";
+
+ /**
+ * Create a new SubscribedFeedsClient.
+ * @param client The GDataClient that should be used to authenticate
+ * requests, retrieve feeds, etc.
+ */
+ public SubscribedFeedsClient(GDataClient client, GDataParserFactory factory) {
+ super(client, factory);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see GDataServiceClient#getServiceName()
+ */
+ public String getServiceName() {
+ return SERVICE;
+ }
+}
diff --git a/src/com/google/wireless/gdata/subscribedfeeds/client/package.html b/src/com/google/wireless/gdata/subscribedfeeds/client/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/subscribedfeeds/client/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/subscribedfeeds/data/FeedUrl.java b/src/com/google/wireless/gdata/subscribedfeeds/data/FeedUrl.java
new file mode 100644
index 0000000..7f90176
--- /dev/null
+++ b/src/com/google/wireless/gdata/subscribedfeeds/data/FeedUrl.java
@@ -0,0 +1,72 @@
+/*
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** See the License for the specific language governing permissions and
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** limitations under the License.
+*/
+
+package com.google.wireless.gdata.subscribedfeeds.data;
+
+/**
+ * The FeedUrl GData type.
+ */
+public class FeedUrl {
+ private String feed;
+ private String service;
+ private String authToken;
+
+ public FeedUrl() {
+ }
+
+ public FeedUrl(String feed, String service, String authToken) {
+ setFeed(feed);
+ setService(service);
+ setAuthToken(authToken);
+ }
+
+ public String getFeed() {
+ return feed;
+ }
+
+ public void setFeed(String feed) {
+ this.feed = feed;
+ }
+
+ public String getService() {
+ return service;
+ }
+
+ public void setService(String service) {
+ this.service = service;
+ }
+
+ public String getAuthToken() {
+ return authToken;
+ }
+
+ public void setAuthToken(String authToken) {
+ this.authToken = authToken;
+ }
+
+ public void toString(StringBuffer sb) {
+ sb.append("FeedUrl");
+ sb.append(" url:").append(getFeed());
+ sb.append(" service:").append(getService());
+ sb.append(" authToken:").append(getAuthToken());
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ toString(sb);
+ return sb.toString();
+ }
+}
diff --git a/src/com/google/wireless/gdata/subscribedfeeds/data/SubscribedFeedsEntry.java b/src/com/google/wireless/gdata/subscribedfeeds/data/SubscribedFeedsEntry.java
new file mode 100644
index 0000000..6d8cbd0
--- /dev/null
+++ b/src/com/google/wireless/gdata/subscribedfeeds/data/SubscribedFeedsEntry.java
@@ -0,0 +1,53 @@
+// Copyright 2007 The Android Open Source Project
+package com.google.wireless.gdata.subscribedfeeds.data;
+
+import com.google.wireless.gdata.data.Entry;
+
+/**
+ * Entry containing information about a contact.
+ */
+public class SubscribedFeedsEntry extends Entry {
+ private FeedUrl feedUrl;
+ private String routingInfo;
+ private String clientToken;
+
+ public String getClientToken() {
+ return clientToken;
+ }
+
+ public void setClientToken(String clientToken) {
+ this.clientToken = clientToken;
+ }
+
+ public SubscribedFeedsEntry() {
+ super();
+ }
+
+ public FeedUrl getSubscribedFeed() {
+ return feedUrl;
+ }
+
+ public void setSubscribedFeed(FeedUrl feedUrl) {
+ this.feedUrl = feedUrl;
+ }
+
+ public String getRoutingInfo() {
+ return routingInfo;
+ }
+
+ public void setRoutingInfo(String routingInfo) {
+ this.routingInfo = routingInfo;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.wireless.gdata.data.Entry#clear()
+ */
+ public void clear() {
+ super.clear();
+ }
+
+ public void toString(StringBuffer sb) {
+ super.toString(sb);
+ }
+}
diff --git a/src/com/google/wireless/gdata/subscribedfeeds/data/SubscribedFeedsFeed.java b/src/com/google/wireless/gdata/subscribedfeeds/data/SubscribedFeedsFeed.java
new file mode 100644
index 0000000..760cdb8
--- /dev/null
+++ b/src/com/google/wireless/gdata/subscribedfeeds/data/SubscribedFeedsFeed.java
@@ -0,0 +1,15 @@
+// Copyright 2007 The Android Open Source Project
+package com.google.wireless.gdata.subscribedfeeds.data;
+
+import com.google.wireless.gdata.data.Feed;
+
+/**
+ * Feed containing contacts.
+ */
+public class SubscribedFeedsFeed extends Feed {
+ /**
+ * Creates a new empty events feed.
+ */
+ public SubscribedFeedsFeed() {
+ }
+}
diff --git a/src/com/google/wireless/gdata/subscribedfeeds/data/package.html b/src/com/google/wireless/gdata/subscribedfeeds/data/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/subscribedfeeds/data/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/subscribedfeeds/package.html b/src/com/google/wireless/gdata/subscribedfeeds/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/subscribedfeeds/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/subscribedfeeds/parser/package.html b/src/com/google/wireless/gdata/subscribedfeeds/parser/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/subscribedfeeds/parser/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/subscribedfeeds/parser/xml/XmlSubscribedFeedsGDataParser.java b/src/com/google/wireless/gdata/subscribedfeeds/parser/xml/XmlSubscribedFeedsGDataParser.java
new file mode 100644
index 0000000..d13449f
--- /dev/null
+++ b/src/com/google/wireless/gdata/subscribedfeeds/parser/xml/XmlSubscribedFeedsGDataParser.java
@@ -0,0 +1,75 @@
+// Copyright 2007 The Android Open Source Project
+package com.google.wireless.gdata.subscribedfeeds.parser.xml;
+
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.data.Feed;
+import com.google.wireless.gdata.data.XmlUtils;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.parser.xml.XmlGDataParser;
+import com.google.wireless.gdata.subscribedfeeds.data.FeedUrl;
+import com.google.wireless.gdata.subscribedfeeds.data.SubscribedFeedsEntry;
+import com.google.wireless.gdata.subscribedfeeds.data.SubscribedFeedsFeed;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * GDataParser for a subscribed feeds feed.
+ */
+public class XmlSubscribedFeedsGDataParser extends XmlGDataParser {
+ /**
+ * Creates a new XmlSubscribedFeedsGDataParser.
+ * @param is The InputStream that should be parsed.
+ * @throws ParseException Thrown if a parser cannot be created.
+ */
+ public XmlSubscribedFeedsGDataParser(InputStream is, XmlPullParser parser)
+ throws ParseException {
+ super(is, parser);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.wireless.gdata.parser.xml.XmlGDataParser#createFeed()
+ */
+ protected Feed createFeed() {
+ return new SubscribedFeedsFeed();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see com.google.wireless.gdata.parser.xml.XmlGDataParser#createEntry()
+ */
+ protected Entry createEntry() {
+ return new SubscribedFeedsEntry();
+ }
+
+ protected void handleExtraElementInEntry(Entry entry)
+ throws XmlPullParserException, IOException {
+ XmlPullParser parser = getParser();
+
+ if (!(entry instanceof SubscribedFeedsEntry)) {
+ throw new IllegalArgumentException("Expected SubscribedFeedsEntry!");
+ }
+ SubscribedFeedsEntry subscribedFeedsEntry =
+ (SubscribedFeedsEntry) entry;
+ String name = parser.getName();
+ if ("feedurl".equals(name)) {
+ FeedUrl feedUrl = new FeedUrl();
+ feedUrl.setFeed(parser.getAttributeValue(null /* ns */, "value"));
+ feedUrl.setService(parser.getAttributeValue(null /* ns */, "service"));
+ feedUrl.setAuthToken(parser.getAttributeValue(null /* ns */, "authtoken"));
+ subscribedFeedsEntry.setSubscribedFeed(feedUrl);
+ }
+ if ("routingInfo".equals(name)) {
+ subscribedFeedsEntry.setRoutingInfo(
+ XmlUtils.extractChildText(parser));
+ }
+ if ("clientToken".equals(name)) {
+ subscribedFeedsEntry.setClientToken(
+ XmlUtils.extractChildText(parser));
+ }
+ }
+}
diff --git a/src/com/google/wireless/gdata/subscribedfeeds/parser/xml/XmlSubscribedFeedsGDataParserFactory.java b/src/com/google/wireless/gdata/subscribedfeeds/parser/xml/XmlSubscribedFeedsGDataParserFactory.java
new file mode 100644
index 0000000..22c755e
--- /dev/null
+++ b/src/com/google/wireless/gdata/subscribedfeeds/parser/xml/XmlSubscribedFeedsGDataParserFactory.java
@@ -0,0 +1,81 @@
+package com.google.wireless.gdata.subscribedfeeds.parser.xml;
+
+import com.google.wireless.gdata.client.GDataParserFactory;
+import com.google.wireless.gdata.data.Entry;
+import com.google.wireless.gdata.parser.GDataParser;
+import com.google.wireless.gdata.parser.ParseException;
+import com.google.wireless.gdata.parser.xml.XmlParserFactory;
+import com.google.wireless.gdata.serializer.GDataSerializer;
+import com.google.wireless.gdata.subscribedfeeds.data.SubscribedFeedsEntry;
+import com.google.wireless.gdata.subscribedfeeds.serializer.xml.XmlSubscribedFeedsEntryGDataSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.InputStream;
+
+/**
+ * GDataParserFactory that creates XML GDataParsers and GDataSerializers for
+ * Subscribed Feeds.
+ */
+public class XmlSubscribedFeedsGDataParserFactory implements
+ GDataParserFactory {
+ private final XmlParserFactory xmlFactory;
+
+ public XmlSubscribedFeedsGDataParserFactory(XmlParserFactory xmlFactory) {
+ this.xmlFactory = xmlFactory;
+ }
+
+ /*
+ * (non-javadoc)
+ *
+ * @see GDataParserFactory#createParser
+ */
+ public GDataParser createParser(InputStream is) throws ParseException {
+ XmlPullParser xmlParser;
+ try {
+ xmlParser = xmlFactory.createParser();
+ } catch (XmlPullParserException xppe) {
+ throw new ParseException("Could not create XmlPullParser", xppe);
+ }
+ return new XmlSubscribedFeedsGDataParser(is, xmlParser);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see GDataParserFactory#createMetaFeedParser(int, java.io.InputStream)
+ */
+ public GDataParser createParser(Class entryClass, InputStream is)
+ throws ParseException {
+ if (entryClass != SubscribedFeedsEntry.class) {
+ throw new IllegalArgumentException(
+ "SubscribedFeeds supports only a single feed type");
+ }
+ // we don't have feed sub-types, so just return the default
+ return createParser(is);
+ }
+
+
+ /**
+ * Creates a new {@link GDataSerializer} for the provided entry. The entry
+ * <strong>must</strong> be an instance of {@link SubscribedFeedsEntry}.
+ *
+ * @param entry The {@link SubscribedFeedsEntry} that should be
+ * serialized.
+ * @return The {@link GDataSerializer} that will serialize this entry.
+ * @throws IllegalArgumentException Thrown if entry is not a
+ * {@link SubscribedFeedsEntry}.
+ * @see com.google.wireless.gdata.client.GDataParserFactory#createSerializer
+ */
+ public GDataSerializer createSerializer(Entry entry) {
+ if (!(entry instanceof SubscribedFeedsEntry)) {
+ throw new IllegalArgumentException(
+ "Expected SubscribedFeedsEntry!");
+ }
+ SubscribedFeedsEntry subscribedFeedsEntry =
+ (SubscribedFeedsEntry) entry;
+ return new XmlSubscribedFeedsEntryGDataSerializer(xmlFactory,
+ subscribedFeedsEntry);
+ }
+}
diff --git a/src/com/google/wireless/gdata/subscribedfeeds/parser/xml/package.html b/src/com/google/wireless/gdata/subscribedfeeds/parser/xml/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/subscribedfeeds/parser/xml/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/subscribedfeeds/serializer/package.html b/src/com/google/wireless/gdata/subscribedfeeds/serializer/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/subscribedfeeds/serializer/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>
diff --git a/src/com/google/wireless/gdata/subscribedfeeds/serializer/xml/XmlSubscribedFeedsEntryGDataSerializer.java b/src/com/google/wireless/gdata/subscribedfeeds/serializer/xml/XmlSubscribedFeedsEntryGDataSerializer.java
new file mode 100644
index 0000000..f957c0b
--- /dev/null
+++ b/src/com/google/wireless/gdata/subscribedfeeds/serializer/xml/XmlSubscribedFeedsEntryGDataSerializer.java
@@ -0,0 +1,80 @@
+package com.google.wireless.gdata.subscribedfeeds.serializer.xml;
+
+import com.google.wireless.gdata.data.StringUtils;
+import com.google.wireless.gdata.serializer.xml.XmlEntryGDataSerializer;
+import com.google.wireless.gdata.subscribedfeeds.data.FeedUrl;
+import com.google.wireless.gdata.subscribedfeeds.data.SubscribedFeedsEntry;
+import com.google.wireless.gdata.parser.xml.XmlParserFactory;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+
+/**
+ * Serializes the SubscribedFeedEntry into the Atom XML format.
+ */
+public class XmlSubscribedFeedsEntryGDataSerializer extends
+ XmlEntryGDataSerializer {
+ public static final String NAMESPACE_GSYNC = "gsync";
+ public static final String NAMESPACE_GSYNC_URI =
+ "http://schemas.google.com/gsync/data";
+
+ public XmlSubscribedFeedsEntryGDataSerializer(XmlParserFactory factory,
+ SubscribedFeedsEntry entry) {
+ super(factory, entry);
+ }
+
+ protected SubscribedFeedsEntry getSubscribedFeedsEntry() {
+ return (SubscribedFeedsEntry) getEntry();
+ }
+
+ protected void declareExtraEntryNamespaces(XmlSerializer serializer)
+ throws IOException {
+ serializer.setPrefix(NAMESPACE_GSYNC, NAMESPACE_GSYNC_URI);
+ }
+
+ /* (non-Javadoc)
+ * @see XmlEntryGDataSerializer#serializeExtraEntryContents
+ */
+ protected void serializeExtraEntryContents(XmlSerializer serializer,
+ int format)
+ throws IOException {
+ SubscribedFeedsEntry entry = getSubscribedFeedsEntry();
+
+ serializeFeedUrl(serializer, entry.getSubscribedFeed());
+ serializeClientToken(serializer, entry.getClientToken());
+ serializeRoutingInfo(serializer, entry.getRoutingInfo());
+ }
+
+ private static void serializeFeedUrl(XmlSerializer serializer,
+ FeedUrl feedUrl)
+ throws IOException {
+ serializer.startTag(NAMESPACE_GSYNC_URI, "feedurl");
+ serializer.attribute(null /* ns */, "value", feedUrl.getFeed());
+ serializer.attribute(null /* ns */, "service", feedUrl.getService());
+ serializer.attribute(null /* ns */, "authtoken", feedUrl.getAuthToken());
+ serializer.endTag(NAMESPACE_GSYNC_URI, "feedurl");
+ }
+
+ private static void serializeClientToken(XmlSerializer serializer,
+ String clientToken)
+ throws IOException {
+ if (StringUtils.isEmpty(clientToken)) {
+ clientToken = "";
+ }
+ serializer.startTag(NAMESPACE_GSYNC_URI, "clientToken");
+ serializer.text(clientToken);
+ serializer.endTag(NAMESPACE_GSYNC_URI, "clientToken");
+ }
+
+ private static void serializeRoutingInfo(XmlSerializer serializer,
+ String routingInfo)
+ throws IOException {
+ if (StringUtils.isEmpty(routingInfo)) {
+ routingInfo = "";
+ }
+ serializer.startTag(NAMESPACE_GSYNC_URI, "routingInfo");
+ serializer.text(routingInfo);
+ serializer.endTag(NAMESPACE_GSYNC_URI, "routingInfo");
+ }
+}
diff --git a/src/com/google/wireless/gdata/subscribedfeeds/serializer/xml/package.html b/src/com/google/wireless/gdata/subscribedfeeds/serializer/xml/package.html
new file mode 100644
index 0000000..1c9bf9d
--- /dev/null
+++ b/src/com/google/wireless/gdata/subscribedfeeds/serializer/xml/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+ {@hide}
+</body>
+</html>