From 27eda2eccd3729366a5545a21238ee9b2960171c Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 21 Oct 2008 07:00:00 -0700 Subject: Initial Contribution --- .../wireless/gdata/ConflictDetectedException.java | 32 ++ src/com/google/wireless/gdata/GDataException.java | 64 +++ .../gdata/calendar/client/CalendarClient.java | 104 ++++ .../wireless/gdata/calendar/client/package.html | 5 + .../gdata/calendar/data/CalendarEntry.java | 162 +++++++ .../gdata/calendar/data/CalendarsFeed.java | 18 + .../wireless/gdata/calendar/data/EventEntry.java | 315 ++++++++++++ .../wireless/gdata/calendar/data/EventsFeed.java | 33 ++ .../wireless/gdata/calendar/data/Recurrence.java | 28 ++ .../wireless/gdata/calendar/data/Reminder.java | 92 ++++ .../google/wireless/gdata/calendar/data/When.java | 53 +++ .../google/wireless/gdata/calendar/data/Who.java | 153 ++++++ .../wireless/gdata/calendar/data/package.html | 5 + .../google/wireless/gdata/calendar/package.html | 5 + .../wireless/gdata/calendar/parser/package.html | 5 + .../parser/xml/XmlCalendarGDataParserFactory.java | 98 ++++ .../parser/xml/XmlCalendarsGDataParser.java | 132 +++++ .../calendar/parser/xml/XmlEventsGDataParser.java | 419 ++++++++++++++++ .../gdata/calendar/parser/xml/package.html | 5 + .../gdata/calendar/serializer/package.html | 5 + .../xml/XmlEventEntryGDataSerializer.java | 399 ++++++++++++++++ .../gdata/calendar/serializer/xml/package.html | 5 + .../client/AllDeletedUnavailableException.java | 36 ++ .../gdata/client/AuthenticationException.java | 37 ++ .../google/wireless/gdata/client/GDataClient.java | 149 ++++++ .../wireless/gdata/client/GDataParserFactory.java | 48 ++ .../wireless/gdata/client/GDataServiceClient.java | 335 +++++++++++++ .../wireless/gdata/client/HttpException.java | 58 +++ .../wireless/gdata/client/HttpQueryParams.java | 73 +++ .../google/wireless/gdata/client/QueryParams.java | 240 ++++++++++ .../gdata/client/ResourceNotFoundException.java | 50 ++ src/com/google/wireless/gdata/client/package.html | 5 + .../gdata/contacts/client/ContactsClient.java | 33 ++ .../wireless/gdata/contacts/client/package.html | 5 + .../wireless/gdata/contacts/data/ContactEntry.java | 218 +++++++++ .../gdata/contacts/data/ContactsElement.java | 71 +++ .../wireless/gdata/contacts/data/ContactsFeed.java | 16 + .../wireless/gdata/contacts/data/EmailAddress.java | 28 ++ .../google/wireless/gdata/contacts/data/GeoPt.java | 70 +++ .../wireless/gdata/contacts/data/GroupEntry.java | 39 ++ .../gdata/contacts/data/GroupMembershipInfo.java | 38 ++ .../wireless/gdata/contacts/data/GroupsFeed.java | 14 + .../wireless/gdata/contacts/data/ImAddress.java | 59 +++ .../wireless/gdata/contacts/data/Organization.java | 43 ++ .../wireless/gdata/contacts/data/PhoneNumber.java | 33 ++ .../gdata/contacts/data/PostalAddress.java | 28 ++ .../wireless/gdata/contacts/data/package.html | 5 + .../google/wireless/gdata/contacts/package.html | 5 + .../wireless/gdata/contacts/parser/package.html | 5 + .../parser/xml/XmlContactsGDataParser.java | 302 ++++++++++++ .../parser/xml/XmlContactsGDataParserFactory.java | 125 +++++ .../parser/xml/XmlGroupEntryGDataParser.java | 60 +++ .../gdata/contacts/parser/xml/package.html | 5 + .../gdata/contacts/serializer/package.html | 5 + .../xml/XmlContactEntryGDataSerializer.java | 243 ++++++++++ .../xml/XmlGroupEntryGDataSerializer.java | 53 +++ .../gdata/contacts/serializer/xml/package.html | 5 + src/com/google/wireless/gdata/data/Entry.java | 291 +++++++++++ .../wireless/gdata/data/ExtendedProperty.java | 53 +++ src/com/google/wireless/gdata/data/Feed.java | 122 +++++ src/com/google/wireless/gdata/data/MediaEntry.java | 10 + .../google/wireless/gdata/data/StringUtils.java | 54 +++ src/com/google/wireless/gdata/data/XmlUtils.java | 133 ++++++ src/com/google/wireless/gdata/data/package.html | 5 + src/com/google/wireless/gdata/package.html | 5 + .../google/wireless/gdata/parser/GDataParser.java | 65 +++ .../wireless/gdata/parser/ParseException.java | 38 ++ src/com/google/wireless/gdata/parser/package.html | 5 + .../gdata/parser/xml/SimplePullParser.java | 300 ++++++++++++ .../wireless/gdata/parser/xml/XmlGDataParser.java | 530 +++++++++++++++++++++ .../gdata/parser/xml/XmlMediaEntryGDataParser.java | 42 ++ .../gdata/parser/xml/XmlParserFactory.java | 31 ++ .../google/wireless/gdata/parser/xml/package.html | 5 + .../wireless/gdata/serializer/GDataSerializer.java | 56 +++ .../google/wireless/gdata/serializer/package.html | 5 + .../serializer/xml/XmlEntryGDataSerializer.java | 268 +++++++++++ .../wireless/gdata/serializer/xml/package.html | 5 + .../spreadsheets/client/SpreadsheetsClient.java | 268 +++++++++++ .../gdata/spreadsheets/client/package.html | 5 + .../gdata/spreadsheets/data/CellEntry.java | 134 ++++++ .../wireless/gdata/spreadsheets/data/CellFeed.java | 35 ++ .../gdata/spreadsheets/data/ListEntry.java | 77 +++ .../wireless/gdata/spreadsheets/data/ListFeed.java | 35 ++ .../gdata/spreadsheets/data/SpreadsheetEntry.java | 38 ++ .../gdata/spreadsheets/data/SpreadsheetFeed.java | 14 + .../gdata/spreadsheets/data/WorksheetEntry.java | 105 ++++ .../gdata/spreadsheets/data/WorksheetFeed.java | 14 + .../wireless/gdata/spreadsheets/data/package.html | 5 + .../wireless/gdata/spreadsheets/package.html | 5 + .../gdata/spreadsheets/parser/package.html | 5 + .../parser/xml/XmlCellsGDataParser.java | 126 +++++ .../parser/xml/XmlListGDataParser.java | 109 +++++ .../parser/xml/XmlSpreadsheetsGDataParser.java | 68 +++ .../xml/XmlSpreadsheetsGDataParserFactory.java | 99 ++++ .../parser/xml/XmlWorksheetsGDataParser.java | 98 ++++ .../gdata/spreadsheets/parser/xml/package.html | 5 + .../gdata/spreadsheets/serializer/package.html | 5 + .../xml/XmlCellEntryGDataSerializer.java | 83 ++++ .../xml/XmlListEntryGDataSerializer.java | 67 +++ .../gdata/spreadsheets/serializer/xml/package.html | 5 + .../client/SubscribedFeedsClient.java | 49 ++ .../gdata/subscribedfeeds/client/package.html | 5 + .../gdata/subscribedfeeds/data/FeedUrl.java | 72 +++ .../subscribedfeeds/data/SubscribedFeedsEntry.java | 53 +++ .../subscribedfeeds/data/SubscribedFeedsFeed.java | 15 + .../gdata/subscribedfeeds/data/package.html | 5 + .../wireless/gdata/subscribedfeeds/package.html | 5 + .../gdata/subscribedfeeds/parser/package.html | 5 + .../parser/xml/XmlSubscribedFeedsGDataParser.java | 75 +++ .../xml/XmlSubscribedFeedsGDataParserFactory.java | 81 ++++ .../gdata/subscribedfeeds/parser/xml/package.html | 5 + .../gdata/subscribedfeeds/serializer/package.html | 5 + .../XmlSubscribedFeedsEntryGDataSerializer.java | 80 ++++ .../subscribedfeeds/serializer/xml/package.html | 5 + 114 files changed, 8564 insertions(+) create mode 100644 src/com/google/wireless/gdata/ConflictDetectedException.java create mode 100644 src/com/google/wireless/gdata/GDataException.java create mode 100644 src/com/google/wireless/gdata/calendar/client/CalendarClient.java create mode 100644 src/com/google/wireless/gdata/calendar/client/package.html create mode 100644 src/com/google/wireless/gdata/calendar/data/CalendarEntry.java create mode 100644 src/com/google/wireless/gdata/calendar/data/CalendarsFeed.java create mode 100644 src/com/google/wireless/gdata/calendar/data/EventEntry.java create mode 100644 src/com/google/wireless/gdata/calendar/data/EventsFeed.java create mode 100644 src/com/google/wireless/gdata/calendar/data/Recurrence.java create mode 100644 src/com/google/wireless/gdata/calendar/data/Reminder.java create mode 100644 src/com/google/wireless/gdata/calendar/data/When.java create mode 100644 src/com/google/wireless/gdata/calendar/data/Who.java create mode 100644 src/com/google/wireless/gdata/calendar/data/package.html create mode 100644 src/com/google/wireless/gdata/calendar/package.html create mode 100644 src/com/google/wireless/gdata/calendar/parser/package.html create mode 100644 src/com/google/wireless/gdata/calendar/parser/xml/XmlCalendarGDataParserFactory.java create mode 100644 src/com/google/wireless/gdata/calendar/parser/xml/XmlCalendarsGDataParser.java create mode 100644 src/com/google/wireless/gdata/calendar/parser/xml/XmlEventsGDataParser.java create mode 100644 src/com/google/wireless/gdata/calendar/parser/xml/package.html create mode 100644 src/com/google/wireless/gdata/calendar/serializer/package.html create mode 100644 src/com/google/wireless/gdata/calendar/serializer/xml/XmlEventEntryGDataSerializer.java create mode 100644 src/com/google/wireless/gdata/calendar/serializer/xml/package.html create mode 100644 src/com/google/wireless/gdata/client/AllDeletedUnavailableException.java create mode 100644 src/com/google/wireless/gdata/client/AuthenticationException.java create mode 100644 src/com/google/wireless/gdata/client/GDataClient.java create mode 100644 src/com/google/wireless/gdata/client/GDataParserFactory.java create mode 100644 src/com/google/wireless/gdata/client/GDataServiceClient.java create mode 100644 src/com/google/wireless/gdata/client/HttpException.java create mode 100644 src/com/google/wireless/gdata/client/HttpQueryParams.java create mode 100644 src/com/google/wireless/gdata/client/QueryParams.java create mode 100644 src/com/google/wireless/gdata/client/ResourceNotFoundException.java create mode 100644 src/com/google/wireless/gdata/client/package.html create mode 100644 src/com/google/wireless/gdata/contacts/client/ContactsClient.java create mode 100644 src/com/google/wireless/gdata/contacts/client/package.html create mode 100644 src/com/google/wireless/gdata/contacts/data/ContactEntry.java create mode 100644 src/com/google/wireless/gdata/contacts/data/ContactsElement.java create mode 100644 src/com/google/wireless/gdata/contacts/data/ContactsFeed.java create mode 100644 src/com/google/wireless/gdata/contacts/data/EmailAddress.java create mode 100644 src/com/google/wireless/gdata/contacts/data/GeoPt.java create mode 100644 src/com/google/wireless/gdata/contacts/data/GroupEntry.java create mode 100644 src/com/google/wireless/gdata/contacts/data/GroupMembershipInfo.java create mode 100644 src/com/google/wireless/gdata/contacts/data/GroupsFeed.java create mode 100644 src/com/google/wireless/gdata/contacts/data/ImAddress.java create mode 100644 src/com/google/wireless/gdata/contacts/data/Organization.java create mode 100644 src/com/google/wireless/gdata/contacts/data/PhoneNumber.java create mode 100644 src/com/google/wireless/gdata/contacts/data/PostalAddress.java create mode 100644 src/com/google/wireless/gdata/contacts/data/package.html create mode 100644 src/com/google/wireless/gdata/contacts/package.html create mode 100644 src/com/google/wireless/gdata/contacts/parser/package.html create mode 100644 src/com/google/wireless/gdata/contacts/parser/xml/XmlContactsGDataParser.java create mode 100644 src/com/google/wireless/gdata/contacts/parser/xml/XmlContactsGDataParserFactory.java create mode 100644 src/com/google/wireless/gdata/contacts/parser/xml/XmlGroupEntryGDataParser.java create mode 100644 src/com/google/wireless/gdata/contacts/parser/xml/package.html create mode 100644 src/com/google/wireless/gdata/contacts/serializer/package.html create mode 100644 src/com/google/wireless/gdata/contacts/serializer/xml/XmlContactEntryGDataSerializer.java create mode 100644 src/com/google/wireless/gdata/contacts/serializer/xml/XmlGroupEntryGDataSerializer.java create mode 100644 src/com/google/wireless/gdata/contacts/serializer/xml/package.html create mode 100644 src/com/google/wireless/gdata/data/Entry.java create mode 100644 src/com/google/wireless/gdata/data/ExtendedProperty.java create mode 100644 src/com/google/wireless/gdata/data/Feed.java create mode 100644 src/com/google/wireless/gdata/data/MediaEntry.java create mode 100644 src/com/google/wireless/gdata/data/StringUtils.java create mode 100644 src/com/google/wireless/gdata/data/XmlUtils.java create mode 100644 src/com/google/wireless/gdata/data/package.html create mode 100644 src/com/google/wireless/gdata/package.html create mode 100644 src/com/google/wireless/gdata/parser/GDataParser.java create mode 100644 src/com/google/wireless/gdata/parser/ParseException.java create mode 100644 src/com/google/wireless/gdata/parser/package.html create mode 100644 src/com/google/wireless/gdata/parser/xml/SimplePullParser.java create mode 100644 src/com/google/wireless/gdata/parser/xml/XmlGDataParser.java create mode 100644 src/com/google/wireless/gdata/parser/xml/XmlMediaEntryGDataParser.java create mode 100644 src/com/google/wireless/gdata/parser/xml/XmlParserFactory.java create mode 100644 src/com/google/wireless/gdata/parser/xml/package.html create mode 100644 src/com/google/wireless/gdata/serializer/GDataSerializer.java create mode 100644 src/com/google/wireless/gdata/serializer/package.html create mode 100644 src/com/google/wireless/gdata/serializer/xml/XmlEntryGDataSerializer.java create mode 100644 src/com/google/wireless/gdata/serializer/xml/package.html create mode 100755 src/com/google/wireless/gdata/spreadsheets/client/SpreadsheetsClient.java create mode 100644 src/com/google/wireless/gdata/spreadsheets/client/package.html create mode 100755 src/com/google/wireless/gdata/spreadsheets/data/CellEntry.java create mode 100755 src/com/google/wireless/gdata/spreadsheets/data/CellFeed.java create mode 100755 src/com/google/wireless/gdata/spreadsheets/data/ListEntry.java create mode 100755 src/com/google/wireless/gdata/spreadsheets/data/ListFeed.java create mode 100755 src/com/google/wireless/gdata/spreadsheets/data/SpreadsheetEntry.java create mode 100755 src/com/google/wireless/gdata/spreadsheets/data/SpreadsheetFeed.java create mode 100644 src/com/google/wireless/gdata/spreadsheets/data/WorksheetEntry.java create mode 100755 src/com/google/wireless/gdata/spreadsheets/data/WorksheetFeed.java create mode 100644 src/com/google/wireless/gdata/spreadsheets/data/package.html create mode 100644 src/com/google/wireless/gdata/spreadsheets/package.html create mode 100644 src/com/google/wireless/gdata/spreadsheets/parser/package.html create mode 100755 src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlCellsGDataParser.java create mode 100755 src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlListGDataParser.java create mode 100755 src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlSpreadsheetsGDataParser.java create mode 100644 src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlSpreadsheetsGDataParserFactory.java create mode 100755 src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlWorksheetsGDataParser.java create mode 100644 src/com/google/wireless/gdata/spreadsheets/parser/xml/package.html create mode 100644 src/com/google/wireless/gdata/spreadsheets/serializer/package.html create mode 100755 src/com/google/wireless/gdata/spreadsheets/serializer/xml/XmlCellEntryGDataSerializer.java create mode 100755 src/com/google/wireless/gdata/spreadsheets/serializer/xml/XmlListEntryGDataSerializer.java create mode 100644 src/com/google/wireless/gdata/spreadsheets/serializer/xml/package.html create mode 100644 src/com/google/wireless/gdata/subscribedfeeds/client/SubscribedFeedsClient.java create mode 100644 src/com/google/wireless/gdata/subscribedfeeds/client/package.html create mode 100644 src/com/google/wireless/gdata/subscribedfeeds/data/FeedUrl.java create mode 100644 src/com/google/wireless/gdata/subscribedfeeds/data/SubscribedFeedsEntry.java create mode 100644 src/com/google/wireless/gdata/subscribedfeeds/data/SubscribedFeedsFeed.java create mode 100644 src/com/google/wireless/gdata/subscribedfeeds/data/package.html create mode 100644 src/com/google/wireless/gdata/subscribedfeeds/package.html create mode 100644 src/com/google/wireless/gdata/subscribedfeeds/parser/package.html create mode 100644 src/com/google/wireless/gdata/subscribedfeeds/parser/xml/XmlSubscribedFeedsGDataParser.java create mode 100644 src/com/google/wireless/gdata/subscribedfeeds/parser/xml/XmlSubscribedFeedsGDataParserFactory.java create mode 100644 src/com/google/wireless/gdata/subscribedfeeds/parser/xml/package.html create mode 100644 src/com/google/wireless/gdata/subscribedfeeds/serializer/package.html create mode 100644 src/com/google/wireless/gdata/subscribedfeeds/serializer/xml/XmlSubscribedFeedsEntryGDataSerializer.java create mode 100644 src/com/google/wireless/gdata/subscribedfeeds/serializer/xml/package.html diff --git a/src/com/google/wireless/gdata/ConflictDetectedException.java b/src/com/google/wireless/gdata/ConflictDetectedException.java new file mode 100644 index 0000000..efd918e --- /dev/null +++ b/src/com/google/wireless/gdata/ConflictDetectedException.java @@ -0,0 +1,32 @@ +// Copyright 2007 The Android Open Source Project + +package com.google.wireless.gdata; + +import com.google.wireless.gdata.data.Entry; + +/** + * A ConflictDetectedException is thrown when the server detects a conflict + * between the Entry which the client is trying to insert or modify and an + * existing Entry. Typically this is because the version of the Entry being + * uploaded by the client is older than the version on the server, but it may + * also indicate the violation of some other constraint (e.g., key uniqueness). + */ +public class ConflictDetectedException extends GDataException { + + private final Entry conflictingEntry; + + /** + * Creates a new ConflictDetectedException with the given entry. + * @param conflictingEntry the conflicting entry state returned by the server. + */ + public ConflictDetectedException(Entry conflictingEntry) { + this.conflictingEntry = conflictingEntry; + } + + /** + * @return the conflicting Entry returned by the server. + */ + public Entry getConflictingEntry() { + return conflictingEntry; + } +} 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..8710079 --- /dev/null +++ b/src/com/google/wireless/gdata/calendar/client/CalendarClient.java @@ -0,0 +1,104 @@ +// 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.AuthenticationException; +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.client.AllDeletedUnavailableException; +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 AuthenticationException, ParseException, IOException, + AllDeletedUnavailableException { + GDataClient gDataClient = getGDataClient(); + try { + InputStream is = gDataClient.getFeedAsStream(feedUrl, authToken); + return getGDataParserFactory().createParser(CalendarEntry.class, is); + } catch (HttpException e) { + convertHttpExceptionForReads("Could not fetch calendars feed", e); + return null; // never reached + } + } +} 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 @@ + + + {@hide} + + 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..098b88c --- /dev/null +++ b/src/com/google/wireless/gdata/calendar/data/CalendarEntry.java @@ -0,0 +1,162 @@ +// 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 contribute (read and write) + * to this calendar. + */ + public static final byte ACCESS_CONTRIBUTOR = 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 @@ + + + {@hide} + + 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 @@ + + + {@hide} + + 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 @@ + + + {@hide} + + 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 + * must 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..2879780 --- /dev/null +++ b/src/com/google/wireless/gdata/calendar/parser/xml/XmlCalendarsGDataParser.java @@ -0,0 +1,132 @@ +// 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)) { + accesslevel = CalendarEntry.ACCESS_CONTRIBUTOR; + } 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..6135d1b --- /dev/null +++ b/src/com/google/wireless/gdata/calendar/parser/xml/XmlEventsGDataParser.java @@ -0,0 +1,419 @@ +// 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 entries in case of + // recurrences, if the recurrences are expanded. + // if the elements precede the elements, we'll only + // process the elements directly under the entry and ignore + // the elements within a . + // if the elements precede the 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 as direct children of the entry or only see + // as children of 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(); + } + + 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 we've seen directly under the + // entry, clear any previously seen reminders (under 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 : 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 : 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 : 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 @@ + + + {@hide} + + 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 @@ + + + {@hide} + + 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 @@ + + + {@hide} + + diff --git a/src/com/google/wireless/gdata/client/AllDeletedUnavailableException.java b/src/com/google/wireless/gdata/client/AllDeletedUnavailableException.java new file mode 100644 index 0000000..038cc87 --- /dev/null +++ b/src/com/google/wireless/gdata/client/AllDeletedUnavailableException.java @@ -0,0 +1,36 @@ +package com.google.wireless.gdata.client; + +import com.google.wireless.gdata.GDataException; + +/** + * Exception thrown when the tombstones for a feed have expired. When the + * client gets this it should refetch the entire feed. + */ +public class AllDeletedUnavailableException extends GDataException { + + /** + * Creates a new AuthenticationException. + */ + public AllDeletedUnavailableException() { + } + + /** + * Creates a new AllDeletedUnavailableException with a supplied message. + * @param message The message for the exception. + */ + public AllDeletedUnavailableException(String message) { + super(message); + } + + /** + * Creates a new AllDeletedUnavailableException with a supplied message and + * underlying cause. + * + * @param message The message for the exception. + * @param cause Another throwable that was caught and wrapped in this + * exception. + */ + public AllDeletedUnavailableException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/com/google/wireless/gdata/client/AuthenticationException.java b/src/com/google/wireless/gdata/client/AuthenticationException.java new file mode 100644 index 0000000..68866ce --- /dev/null +++ b/src/com/google/wireless/gdata/client/AuthenticationException.java @@ -0,0 +1,37 @@ +// Copyright 2007 The Android Open Source Project + +package com.google.wireless.gdata.client; + +import com.google.wireless.gdata.GDataException; + +/** + * Exception thrown when a user's credentials could not be authenticated. + */ +public class AuthenticationException extends GDataException { + + /** + * Creates a new AuthenticationException. + */ + public AuthenticationException() { + } + + /** + * Creates a new AuthenticationException with a supplied message. + * @param message The message for the exception. + */ + public AuthenticationException(String message) { + super(message); + } + + /** + * Creates a new AuthenticationException with a supplied message and + * underlying cause. + * + * @param message The message for the exception. + * @param cause Another throwable that was caught and wrapped in this + * exception. + */ + public AuthenticationException(String message, Throwable cause) { + super(message, cause); + } +} 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..8b32885 --- /dev/null +++ b/src/com/google/wireless/gdata/client/GDataServiceClient.java @@ -0,0 +1,335 @@ +// 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.StringUtils; +import com.google.wireless.gdata.data.MediaEntry; +import com.google.wireless.gdata.ConflictDetectedException; +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; + + // TODO: remove this, after coordinating with other developers. + private static final boolean PARSE_CONFLICTING_ENTRIES = false; + + 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 The 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 AuthenticationException Thrown if the server considers the + * authToken invalid. + * @throws ParseException Thrown if the server response cannot be parsed. + * @throws IOException Thrown if an error occurs while communicating with + * the GData service. + */ + public GDataParser getParserForFeed(Class feedEntryClass, String feedUrl, String authToken) + throws AuthenticationException, ParseException, IOException, + AllDeletedUnavailableException { + try { + InputStream is = gDataClient.getFeedAsStream(feedUrl, authToken); + return gDataParserFactory.createParser(feedEntryClass, is); + } catch (HttpException e) { + convertHttpExceptionForReads("Could not fetch feed.", e); + return null; // never reached + } + } + + /** + * 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 AuthenticationException Thrown if the server considers the + * authToken invalid. + * @throws IOException Thrown if an error occurs while communicating with + * the GData service. + */ + public InputStream getMediaEntryAsStream(String mediaEntryUrl, String authToken) + throws AuthenticationException, IOException, ResourceNotFoundException { + try { + return gDataClient.getMediaEntryAsStream(mediaEntryUrl, authToken); + } catch (HttpException e) { + convertHttpExceptionForMediaEntries("Could not fetch media entry " + mediaEntryUrl, e); + return null; // never reached + } + } + + /** + * 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 AuthenticationException Thrown if the server considers the + * authToken invalid. + * @throws ParseException Thrown if the server response cannot be parsed. + * @throws IOException Thrown if an error occurs while communicating with + * the GData service. + * @throws ConflictDetectedException Thrown if the server detects an + * existing entry that conflicts with this one. + */ + public Entry createEntry(String feedUrl, String authToken, Entry entry) + throws AuthenticationException, ParseException, IOException, + ConflictDetectedException { + GDataSerializer serializer = gDataParserFactory.createSerializer(entry); + try { + InputStream is = gDataClient.createEntry(feedUrl, authToken, serializer); + return parseEntry(entry.getClass(), is); + } catch (HttpException e) { + convertHttpExceptionForWrites(entry.getClass(), "Could not create entry.", e); + return null; // never reached. + } + } + + /** + * 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 AuthenticationException Thrown if the server considers the + * authToken invalid. + * @throws ParseException Thrown if the server response cannot be parsed. + * @throws IOException Thrown if an error occurs while communicating with + * the GData service. + */ + public Entry getEntry(Class entryClass, String id, String authToken) + throws AuthenticationException, ParseException, IOException, + AllDeletedUnavailableException { + try { + InputStream is = getGDataClient().getFeedAsStream(id, authToken); + return parseEntry(entryClass, is); + } catch (HttpException e) { + convertHttpExceptionForReads("Could not fetch entry.", e); + return null; // never reached + } + } + + /** + * 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 AuthenticationException Thrown if the server considers the + * authToken invalid. + * @throws ParseException Thrown if the server response cannot be parsed. + * @throws IOException Thrown if an error occurs while communicating with + * the GData service. + * @throws ConflictDetectedException Thrown if the server detects an + * existing entry that conflicts with this one, or if the server version of + * this entry has changed since it was retrieved. + */ + public Entry updateEntry(Entry entry, String authToken) + throws AuthenticationException, ParseException, IOException, + ConflictDetectedException { + String editUri = entry.getEditUri(); + if (StringUtils.isEmpty(editUri)) { + throw new ParseException("No edit URI -- cannot update."); + } + + GDataSerializer serializer = gDataParserFactory.createSerializer(entry); + try { + InputStream is = gDataClient.updateEntry(editUri, + authToken, + serializer); + return parseEntry(entry.getClass(), is); + } catch (HttpException e) { + if (e.getStatusCode() == HttpException.SC_NOT_FOUND) { + throw new ParseException("Could not update entry.", e); + } + convertHttpExceptionForWrites(entry.getClass(), "Could not update entry.", e); + return null; // never reached + } + } + + /** + * 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 AuthenticationException Thrown if the server considers the + * authToken invalid. + * @throws ParseException Thrown if the server response cannot be parsed. + * @throws IOException Thrown if an error occurs while communicating with + * the GData service. + * @throws ConflictDetectedException Thrown if the server detects an + * existing entry that conflicts with this one, or if the server version of + * this entry has changed since it was retrieved. + */ + public MediaEntry updateMediaEntry(String editUri, InputStream inputStream, + String contentType, + String authToken) + throws AuthenticationException, ParseException, IOException, + ConflictDetectedException { + if (StringUtils.isEmpty(editUri)) { + throw new ParseException("No edit URI -- cannot update."); + } + + try { + InputStream is = gDataClient.updateMediaEntry(editUri, authToken, + inputStream, contentType); + return (MediaEntry)parseEntry(MediaEntry.class, is); + } catch (HttpException e) { + convertHttpExceptionForWrites(MediaEntry.class, "Could not update entry.", e); + return null; // never reached + } + } + + /** + * Deletes an existing entry. + * + * @param editUri The editUri for the entry that should be deleted. + * @param authToken The authentication token for this user. + * @throws AuthenticationException Thrown if the server considers the + * authToken invalid. + * @throws ParseException Thrown if the server response cannot be parsed. + * @throws IOException Thrown if an error occurs while communicating with + * the GData service. + * @throws ConflictDetectedException Thrown if the server version of + * this entry has changed since it was retrieved. + */ + public void deleteEntry(String editUri, String authToken) + throws AuthenticationException, ConflictDetectedException, + ParseException, IOException { + try { + gDataClient.deleteEntry(editUri, authToken); + } catch (HttpException e) { + if (e.getStatusCode() == HttpException.SC_NOT_FOUND) { + // the server does not know about this entry. + // nothing to delete. + return; + } + convertHttpExceptionForWrites(null, "Unable to delete", e); + } + } + + 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(); + } + } + } + + protected void convertHttpExceptionForReads(String message, HttpException cause) + throws AuthenticationException, IOException, AllDeletedUnavailableException { + switch (cause.getStatusCode()) { + case HttpException.SC_FORBIDDEN: + case HttpException.SC_UNAUTHORIZED: + throw new AuthenticationException(message, cause); + case HttpException.SC_GONE: + throw new AllDeletedUnavailableException(message, cause); + default: + throw new IOException(message + ": " + cause.getMessage()); + } + } + + protected void convertHttpExceptionForMediaEntries(String message, HttpException cause) + throws AuthenticationException, IOException, ResourceNotFoundException { + switch (cause.getStatusCode()) { + case HttpException.SC_FORBIDDEN: + case HttpException.SC_UNAUTHORIZED: + throw new AuthenticationException(message, cause); + case HttpException.SC_NOT_FOUND: + throw new ResourceNotFoundException(message, cause); + default: + throw new IOException(message + ": " + cause.getMessage()); + } + } + + protected void convertHttpExceptionForWrites(Class entryClass, String message, + HttpException cause) throws ConflictDetectedException, + AuthenticationException, ParseException, IOException { + switch (cause.getStatusCode()) { + case HttpException.SC_CONFLICT: + Entry entry = null; + if (PARSE_CONFLICTING_ENTRIES) { + if (entryClass != null) { + InputStream is = cause.getResponseStream(); + if (is != null) { + entry = parseEntry(entryClass, cause.getResponseStream()); + } + } + } + throw new ConflictDetectedException(entry); + case HttpException.SC_BAD_REQUEST: + throw new ParseException(message + ": " + cause.getMessage()); + case HttpException.SC_FORBIDDEN: + case HttpException.SC_UNAUTHORIZED: + throw new AuthenticationException(message, cause); + default: + throw new IOException(message + ": " + cause.getMessage()); + } + } +} 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/ResourceNotFoundException.java b/src/com/google/wireless/gdata/client/ResourceNotFoundException.java new file mode 100644 index 0000000..86c3040 --- /dev/null +++ b/src/com/google/wireless/gdata/client/ResourceNotFoundException.java @@ -0,0 +1,50 @@ +/* +** Copyright 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, +** 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.client; + +import com.google.wireless.gdata.GDataException; + +/** + * Exception thrown when a specified resource does not exist + */ +public class ResourceNotFoundException extends GDataException { + + /** + * Creates a new ResourceNotFoundException. + */ + public ResourceNotFoundException() { + } + + /** + * Creates a new ResourceNotFoundException with a supplied message. + * @param message The message for the exception. + */ + public ResourceNotFoundException(String message) { + super(message); + } + + /** + * Creates a new ResourceNotFoundException with a supplied message and + * underlying cause. + * + * @param message The message for the exception. + * @param cause Another throwable that was caught and wrapped in this + * exception. + */ + public ResourceNotFoundException(String message, Throwable cause) { + super(message, cause); + } +} 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 @@ + + + {@hide} + + 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 @@ + + + {@hide} + + 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 @@ + + + {@hide} + + 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 @@ + + + {@hide} + + 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 @@ + + + {@hide} + + 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 + * must 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 @@ + + + {@hide} + + 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 @@ + + + {@hide} + + 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 @@ + + + {@hide} + + 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: ": \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 @@ + + + {@hide} + + 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 @@ + + + {@hide} + + 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 "pull" 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 @@ + + + {@hide} + + 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:
    + *
  • it is easier to use robustly because it makes it trivial to handle unexpected tags (which + * might have children)
  • + *
  • it makes the handling of text (cdata) blocks more convenient
  • + *
  • 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) + *
+ */ +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 .", + xppe); + } catch (IOException ioe) { + throw new ParseException("Unable to parse .", + 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 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 + // 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 <feed> 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 : 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, 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, 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 .", + xppe); + } catch (IOException ioe) { + throw new ParseException("Unable to parse .", + 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 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 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 <entry>. + * + * @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 : 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 <entry> + * 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) <link>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 @@ + + + {@hide} + + 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 @@ + + + {@hide} + + 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 @@ + + + {@hide} + + 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..8b19918 --- /dev/null +++ b/src/com/google/wireless/gdata/spreadsheets/client/SpreadsheetsClient.java @@ -0,0 +1,268 @@ +// Copyright 2007 The Android Open Source Project +package com.google.wireless.gdata.spreadsheets.client; + +import com.google.wireless.gdata.ConflictDetectedException; +import com.google.wireless.gdata.client.AuthenticationException; +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.AllDeletedUnavailableException; +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 AuthenticationException if the authToken is not valid + * @throws ParseException if the response from the server could not be + * parsed + */ + private GDataParser getParserForTypedFeed(Class feedEntryClass, String feedUri, + String authToken) throws AuthenticationException, + ParseException, IOException, AllDeletedUnavailableException { + GDataClient gDataClient = getGDataClient(); + GDataParserFactory gDataParserFactory = getGDataParserFactory(); + + try { + InputStream is = gDataClient.getFeedAsStream(feedUri, authToken); + return gDataParserFactory.createParser(feedEntryClass, is); + } catch (HttpException e) { + convertHttpExceptionForReads("Could not fetch parser feed.", e); + return null; // never reached + } + } + + /* (non-javadoc) + * @see GDataServiceClient#createEntry + */ + public Entry createEntry(String feedUri, String authToken, Entry entry) + throws AuthenticationException, ConflictDetectedException, + ParseException, IOException { + + GDataParserFactory factory = getGDataParserFactory(); + GDataSerializer serializer = factory.createSerializer(entry); + + InputStream is; + try { + is = getGDataClient().createEntry(feedUri, authToken, + serializer); + } catch (HttpException e) { + convertHttpExceptionForWrites(entry.getClass(), "Could not update entry.", e); + return null; // never reached. + } + + 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 AuthenticationException if the authToken is not valid + * @throws ParseException if the response from the server could not be + * parsed + */ + public GDataParser getParserForCellsFeed(String feedUri, String authToken) + throws AuthenticationException, ConflictDetectedException, + ParseException, IOException, AllDeletedUnavailableException { + 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 AuthenticationException if the authToken is not valid + * @throws ParseException if the response from the server could not be + * parsed + */ + public GDataParser getParserForFeed(Class feedEntryClass, String feedUri, String authToken) + throws AuthenticationException, ParseException, IOException, + AllDeletedUnavailableException { + return getParserForTypedFeed(SpreadsheetEntry.class, feedUri, authToken); + } + + /** + * 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 AuthenticationException if the authToken is not valid + * @throws ParseException if the response from the server could not be + * parsed + */ + public GDataParser getParserForListFeed(String feedUri, String authToken) + throws AuthenticationException, ParseException, IOException, + AllDeletedUnavailableException { + 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 AuthenticationException if the authToken is not valid + * @throws ParseException if the response from the server could not be + * parsed + */ + public GDataParser getParserForSpreadsheetsFeed(String feedUri, + String authToken) + throws AuthenticationException, ParseException, IOException, + AllDeletedUnavailableException { + 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 AuthenticationException if the authToken is not valid + * @throws ParseException if the response from the server could not be + * parsed + */ + public GDataParser getParserForWorksheetsFeed(String feedUri, + String authToken) + throws AuthenticationException, ParseException, IOException, + AllDeletedUnavailableException { + return getParserForTypedFeed(WorksheetEntry.class, feedUri, authToken); + } + + /** + * Updates an entry. The URI to be updated is taken from + * entry. Note that only entries in List and Cells feeds + * can be updated, so entry 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 AuthenticationException if the authToken is invalid + * @throws ParseException if the server returned an error, if the server's + * response was unparseable (unlikely), or if entry + * is of a read-only type + * @throws IOException on network error + */ + public Entry updateEntry(Entry entry, String authToken) + throws AuthenticationException, ConflictDetectedException, + ParseException, IOException { + 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; + try { + is = getGDataClient().updateEntry(editUri, + authToken, + serializer); + } catch (HttpException e) { + convertHttpExceptionForWrites(entry.getClass(), "Could not update entry.", e); + return null; // never reached + } + + 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 @@ + + + {@hide} + + 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 + * getValue(name, null). + * + * @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 @@ + + + {@hide} + + 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 @@ + + + {@hide} + + 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 @@ + + + {@hide} + + 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: + // data + // 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 @@ + + + {@hide} + + 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 @@ + + + {@hide} + + 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 @@ + + + {@hide} + + 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..632e8e3 --- /dev/null +++ b/src/com/google/wireless/gdata/subscribedfeeds/client/SubscribedFeedsClient.java @@ -0,0 +1,49 @@ +// Copyright 2007 The Android Open Source Project + +package com.google.wireless.gdata.subscribedfeeds.client; + +import com.google.wireless.gdata.client.AuthenticationException; +import com.google.wireless.gdata.client.GDataClient; +import com.google.wireless.gdata.client.GDataServiceClient; +import com.google.wireless.gdata.client.QueryParams; +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.serializer.xml.XmlEntryGDataSerializer; +import com.google.wireless.gdata.subscribedfeeds.data.SubscribedFeedsEntry; +import com.google.wireless.gdata.subscribedfeeds.parser.xml.XmlSubscribedFeedsGDataParser; +import com.google.wireless.gdata.subscribedfeeds.serializer.xml.XmlSubscribedFeedsEntryGDataSerializer; + +import java.io.IOException; +import java.io.InputStream; + +/** + * 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 @@ + + + {@hide} + + 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 @@ + + + {@hide} + + 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 @@ + + + {@hide} + + 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 @@ + + + {@hide} + + 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 + * must 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 @@ + + + {@hide} + + 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 @@ + + + {@hide} + + 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 @@ + + + {@hide} + + -- cgit v1.2.3