diff options
author | Jean-Baptiste Queru <jbq@google.com> | 2009-11-12 18:45:26 -0800 |
---|---|---|
committer | Jean-Baptiste Queru <jbq@google.com> | 2009-11-12 18:45:26 -0800 |
commit | 874caa518dee0a761ba5102a2b81ef84fefd2181 (patch) | |
tree | b0417c48c9d1384c72027efe4f6d68ec686ce127 | |
parent | a8d14b5ad6306e65266c1801dabb660f8d4a04a1 (diff) | |
download | gdata-874caa518dee0a761ba5102a2b81ef84fefd2181.tar.gz |
eclair snapshot
102 files changed, 8520 insertions, 1342 deletions
diff --git a/src/com/google/wireless/gdata/calendar/data/CalendarEntry.java b/src/com/google/wireless/gdata/calendar/data/CalendarEntry.java index 6e49a9f..0d9e8d8 100644 --- a/src/com/google/wireless/gdata/calendar/data/CalendarEntry.java +++ b/src/com/google/wireless/gdata/calendar/data/CalendarEntry.java @@ -35,6 +35,11 @@ public class CalendarEntry extends Entry { * Access level constant indicating the user owns this calendar. */ public static final byte ACCESS_OWNER = 4; + + /** + * Access level constant indicating the user is a domain admin. + */ + public static final byte ACCESS_ROOT = 5; private byte accessLevel = ACCESS_READ; // TODO: rename to feed Url? diff --git a/src/com/google/wireless/gdata/calendar/data/EventEntry.java b/src/com/google/wireless/gdata/calendar/data/EventEntry.java index 5f2f271..329d6f4 100644 --- a/src/com/google/wireless/gdata/calendar/data/EventEntry.java +++ b/src/com/google/wireless/gdata/calendar/data/EventEntry.java @@ -70,6 +70,11 @@ public class EventEntry extends Entry { private byte visibility = VISIBILITY_DEFAULT; private byte transparency = TRANSPARENCY_OPAQUE; private Vector attendees = new Vector(); + private boolean sendEventNotifications = false; + private boolean guestsCanModify = false; + private boolean guestsCanInviteOthers = true; + private boolean guestsCanSeeGuests = true; + private String organizer = null; private Vector whens = new Vector(); private Vector reminders = null; private String originalEventId = null; @@ -77,6 +82,7 @@ public class EventEntry extends Entry { private String where = null; private String commentsUri = null; private Hashtable extendedProperties = null; + private boolean quickAdd = false; /** * Creates a new empty event entry. @@ -94,6 +100,11 @@ public class EventEntry extends Entry { recurrence = null; visibility = VISIBILITY_DEFAULT; transparency = TRANSPARENCY_OPAQUE; + sendEventNotifications = false; + guestsCanModify = false; + guestsCanInviteOthers = true; + guestsCanSeeGuests = true; + organizer = null; attendees.removeAllElements(); whens.removeAllElements(); reminders = null; @@ -102,6 +113,7 @@ public class EventEntry extends Entry { where = null; commentsUri = null; extendedProperties = null; + quickAdd = false; } /** @@ -160,6 +172,46 @@ public class EventEntry extends Entry { this.visibility = visibility; } + public boolean getSendEventNotifications() { + return sendEventNotifications; + } + + public void setSendEventNotifications(boolean sendEventNotifications) { + this.sendEventNotifications = sendEventNotifications; + } + + public boolean getGuestsCanModify() { + return guestsCanModify; + } + + public void setGuestsCanModify(boolean guestsCanModify) { + this.guestsCanModify = guestsCanModify; + } + + public boolean getGuestsCanInviteOthers() { + return guestsCanInviteOthers; + } + + public void setGuestsCanInviteOthers(boolean guestsCanInviteOthers) { + this.guestsCanInviteOthers = guestsCanInviteOthers; + } + + public boolean getGuestsCanSeeGuests() { + return guestsCanSeeGuests; + } + + public void setGuestsCanSeeGuests(boolean guestsCanSeeGuests) { + this.guestsCanSeeGuests = guestsCanSeeGuests; + } + + public String getOrganizer() { + return organizer; + } + + public void setOrganizer(String organizer) { + this.organizer = organizer; + } + public void clearAttendees() { attendees.clear(); } @@ -270,15 +322,29 @@ public class EventEntry extends Entry { this.commentsUri = commentsUri; } + public boolean isQuickAdd() { + return quickAdd; + } + + public void setQuickAdd(boolean quickAdd) { + this.quickAdd = quickAdd; + } + 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); + sb.append("QUICK_ADD: " + (quickAdd ? "true" : "false")); + sb.append("SEND_EVENT_NOTIFICATIONS: " + (sendEventNotifications ? "true" : "false")); + sb.append("GUESTS_CAN_MODIFY: " + (guestsCanModify ? "true" : "false")); + sb.append("GUESTS_CAN_INVITE_OTHERS: " + (guestsCanInviteOthers ? "true" : "false")); + sb.append("GUESTS_CAN_SEE_GUESTS: " + (guestsCanSeeGuests ? "true" : "false")); + appendIfNotNull(sb, "ORGANIZER", organizer); Enumeration whos = this.attendees.elements(); while (whos.hasMoreElements()) { diff --git a/src/com/google/wireless/gdata/calendar/parser/xml/XmlCalendarsGDataParser.java b/src/com/google/wireless/gdata/calendar/parser/xml/XmlCalendarsGDataParser.java index 431fd43..9619834 100644 --- a/src/com/google/wireless/gdata/calendar/parser/xml/XmlCalendarsGDataParser.java +++ b/src/com/google/wireless/gdata/calendar/parser/xml/XmlCalendarsGDataParser.java @@ -81,6 +81,8 @@ public class XmlCalendarsGDataParser extends XmlGDataParser { accesslevel = CalendarEntry.ACCESS_EDITOR; } else if ("owner".equals(accesslevelStr)) { accesslevel = CalendarEntry.ACCESS_OWNER; + } else if ("root".equals(accesslevelStr)) { + accesslevel = CalendarEntry.ACCESS_ROOT; } calendarEntry.setAccessLevel(accesslevel); } else if ("color".equals(name)) { diff --git a/src/com/google/wireless/gdata/calendar/parser/xml/XmlEventsGDataParser.java b/src/com/google/wireless/gdata/calendar/parser/xml/XmlEventsGDataParser.java index 7ec447e..eddbc3f 100644 --- a/src/com/google/wireless/gdata/calendar/parser/xml/XmlEventsGDataParser.java +++ b/src/com/google/wireless/gdata/calendar/parser/xml/XmlEventsGDataParser.java @@ -152,7 +152,23 @@ public class XmlEventsGDataParser extends XmlGDataParser { eventEntry.setVisibility(visibility); } else if ("who".equals(name)) { handleWho(eventEntry); - } else if ("when".equals(name)) { + } else if ("sendEventNotifications".equals(name)) { + // TODO: check that the namespace is gCal + String value = parser.getAttributeValue(null /* ns */, "value"); + eventEntry.setSendEventNotifications("true".equals(value)); + } else if ("guestsCanModify".equals(name)) { + // TODO: check that the namespace is gCal + String value = parser.getAttributeValue(null /* ns */, "value"); + eventEntry.setGuestsCanModify("true".equals(value)); + } else if ("guestsCanInviteOthers".equals(name)) { + // TODO: check that the namespace is gCal + String value = parser.getAttributeValue(null /* ns */, "value"); + eventEntry.setGuestsCanInviteOthers("true".equals(value)); + } else if ("guestsCanSeeGuests".equals(name)) { + // TODO: check that the namespace is gCal + String value = parser.getAttributeValue(null /* ns */, "value"); + eventEntry.setGuestsCanSeeGuests("true".equals(value)); + } else if ("when".equals(name)) { handleWhen(eventEntry); } else if ("reminder".equals(name)) { if (!hasSeenReminder) { diff --git a/src/com/google/wireless/gdata/calendar/serializer/xml/XmlEventEntryGDataSerializer.java b/src/com/google/wireless/gdata/calendar/serializer/xml/XmlEventEntryGDataSerializer.java index ccb701f..3fe8c27 100644 --- a/src/com/google/wireless/gdata/calendar/serializer/xml/XmlEventEntryGDataSerializer.java +++ b/src/com/google/wireless/gdata/calendar/serializer/xml/XmlEventEntryGDataSerializer.java @@ -54,6 +54,20 @@ public class XmlEventEntryGDataSerializer extends XmlEntryGDataSerializer { serializeEventStatus(serializer, entry.getStatus()); serializeTransparency(serializer, entry.getTransparency()); serializeVisibility(serializer, entry.getVisibility()); + if (entry.getSendEventNotifications()) { + serializer.startTag(NAMESPACE_GCAL_URI, "sendEventNotifications"); + serializer.attribute(null /* ns */, "value", "true"); + serializer.endTag(NAMESPACE_GCAL_URI, "sendEventNotifications"); + } + + // TODO: sending these values can cause server crashes, e.g. if modifying attendee + // status while sending guestsCanModify=false + /* + serializeGuestsCanModify(serializer, entry.getGuestsCanModify()); + serializeGuestsCanInviteOthers(serializer, entry.getGuestsCanInviteOthers()); + serializeGuestsCanSeeGuests(serializer, entry.getGuestsCanSeeGuests()); + */ + Enumeration attendees = entry.getAttendees().elements(); while (attendees.hasMoreElements()) { Who attendee = (Who) attendees.nextElement(); @@ -94,6 +108,8 @@ public class XmlEventEntryGDataSerializer extends XmlEntryGDataSerializer { serializeExtendedProperty(serializer, propertyName, propertyValue); } } + + serializeQuickAdd(serializer, entry.isQuickAdd()); } private static void serializeEventStatus(XmlSerializer serializer, @@ -193,7 +209,39 @@ public class XmlEventEntryGDataSerializer extends XmlEntryGDataSerializer { serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "visibility"); } - private static void serializeWho(XmlSerializer serializer, + private static void serializeSendEventNotifications(XmlSerializer serializer, + boolean sendEventNotifications) + throws IOException { + serializer.startTag(NAMESPACE_GCAL_URI, "sendEventNotifications"); + serializer.attribute(null /* ns */, "value", sendEventNotifications ? "true" : "false"); + serializer.endTag(NAMESPACE_GCAL_URI, "sendEventNotifications"); + } + + private static void serializeGuestsCanModify(XmlSerializer serializer, + boolean guestsCanModify) + throws IOException { + serializer.startTag(NAMESPACE_GCAL_URI, "guestsCanModify"); + serializer.attribute(null /* ns */, "value", guestsCanModify ? "true" : "false"); + serializer.endTag(NAMESPACE_GCAL_URI, "guestsCanModify"); + } + + private static void serializeGuestsCanInviteOthers(XmlSerializer serializer, + boolean guestsCanInviteOthers) + throws IOException { + serializer.startTag(NAMESPACE_GCAL_URI, "guestsCanInviteOthers"); + serializer.attribute(null /* ns */, "value", guestsCanInviteOthers ? "true" : "false"); + serializer.endTag(NAMESPACE_GCAL_URI, "guestsCanInviteOthers"); + } + + private static void serializeGuestsCanSeeGuests(XmlSerializer serializer, + boolean guestsCanSeeGuests) + throws IOException { + serializer.startTag(NAMESPACE_GCAL_URI, "guestsCanSeeGuests"); + serializer.attribute(null /* ns */, "value", guestsCanSeeGuests ? "true" : "false"); + serializer.endTag(NAMESPACE_GCAL_URI, "guestsCanSeeGuests"); + } + + private static void serializeWho(XmlSerializer serializer, EventEntry entry, Who who) throws IOException, ParseException { @@ -396,4 +444,13 @@ public class XmlEventEntryGDataSerializer extends XmlEntryGDataSerializer { serializer.attribute(null /* ns */, "value", value); serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, "extendedProperty"); } + + private static void serializeQuickAdd(XmlSerializer serializer, + boolean quickAdd) throws IOException { + if (quickAdd) { + serializer.startTag(NAMESPACE_GCAL, "quickadd"); + serializer.attribute(null /* ns */, "value", "true"); + serializer.endTag(NAMESPACE_GCAL, "quickadd"); + } + } } diff --git a/src/com/google/wireless/gdata/client/HttpQueryParams.java b/src/com/google/wireless/gdata/client/HttpQueryParams.java index 4878002..e391e27 100644 --- a/src/com/google/wireless/gdata/client/HttpQueryParams.java +++ b/src/com/google/wireless/gdata/client/HttpQueryParams.java @@ -43,8 +43,10 @@ public class HttpQueryParams extends QueryParams { url.append('&'); } String name = (String) names.elementAt(i); + String value = getParamValue(name); + if (value == null) continue; url.append(client.encodeUri(name)).append('='); - url.append(client.encodeUri(getParamValue(name))); + url.append(client.encodeUri(value)); } return url.toString(); } diff --git a/src/com/google/wireless/gdata/data/XmlUtils.java b/src/com/google/wireless/gdata/data/XmlUtils.java index 1c067d3..d838372 100644 --- a/src/com/google/wireless/gdata/data/XmlUtils.java +++ b/src/com/google/wireless/gdata/data/XmlUtils.java @@ -77,6 +77,30 @@ public final class XmlUtils { + "depth " + parentDepth); } + /** + * Supply a 'skipSubTree' API which, for some reason, the kxml2 pull parser + * hasn't implemented. + */ + public void skipSubTree(XmlPullParser parser) + throws XmlPullParserException, IOException { + // Iterate the remaining structure for this element, discarding events + // until we hit the element's corresponding end tag. + int level = 1; + while (level > 0) { + int eventType = parser.next(); + switch (eventType) { + case XmlPullParser.START_TAG: + ++level; + break; + case XmlPullParser.END_TAG: + --level; + break; + default: + break; + } + } + } + // public static void parseChildrenToSerializer(XmlPullParser parser, XmlSerializer serializer) // throws XmlPullParserException, IOException { // int parentDepth = parser.getDepth(); diff --git a/src/com/google/wireless/gdata/parser/ConflictException.java b/src/com/google/wireless/gdata/parser/ConflictException.java new file mode 100644 index 0000000..87e7539 --- /dev/null +++ b/src/com/google/wireless/gdata/parser/ConflictException.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 entry cannot be modified due to a version conflict. + */ +public class ConflictException extends GDataException { + + /** + * Creates a new empty ConflictException. + */ + public ConflictException() { + super(); + } + + /** + * Creates a new ConflictException with the supplied message. + * @param message The message for this ConflictException. + */ + public ConflictException(String message) { + super(message); + } + + /** + * Creates a new ConflictException with the supplied message and underlying + * cause. + * + * @param message The message for this ConflictException. + * @param cause The underlying cause that was caught and wrapped by this + * ConflictException. + */ + public ConflictException(String message, Throwable cause) { + super(message, cause); + } +}
\ No newline at end of file diff --git a/src/com/google/wireless/gdata/spreadsheets/client/SpreadsheetsClient.java b/src/com/google/wireless/gdata/spreadsheets/client/SpreadsheetsClient.java deleted file mode 100755 index 388856d..0000000 --- a/src/com/google/wireless/gdata/spreadsheets/client/SpreadsheetsClient.java +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright 2007 The Android Open Source Project -package com.google.wireless.gdata.spreadsheets.client; - -import com.google.wireless.gdata.client.GDataClient; -import com.google.wireless.gdata.client.GDataParserFactory; -import com.google.wireless.gdata.client.GDataServiceClient; -import com.google.wireless.gdata.client.HttpException; -import com.google.wireless.gdata.data.Entry; -import com.google.wireless.gdata.data.StringUtils; -import com.google.wireless.gdata.parser.GDataParser; -import com.google.wireless.gdata.parser.ParseException; -import com.google.wireless.gdata.serializer.GDataSerializer; -import com.google.wireless.gdata.spreadsheets.data.CellEntry; -import com.google.wireless.gdata.spreadsheets.data.ListEntry; -import com.google.wireless.gdata.spreadsheets.data.SpreadsheetEntry; -import com.google.wireless.gdata.spreadsheets.data.WorksheetEntry; - -import java.io.IOException; -import java.io.InputStream; - -/** - * GDataServiceClient for accessing Google Spreadsheets. This client can - * access and parse all of the Spreadsheets feed types: Spreadsheets feed, - * Worksheets feed, List feed, and Cells feed. Read operations are supported - * on all feed types, but only the List and Cells feeds support write - * operations. (This is a limitation of the protocol, not this API. Such write - * access may be added to the protocol in the future, requiring changes to - * this implementation.) - * - * Only 'private' visibility and 'full' projections are currently supported. - */ -public class SpreadsheetsClient extends GDataServiceClient { - /** The name of the service, dictated to be 'wise' by the protocol. */ - private static final String SERVICE = "wise"; - - /** Standard base feed url for spreadsheets. */ - public static final String SPREADSHEETS_BASE_FEED_URL = - "http://spreadsheets.google.com/feeds/spreadsheets/private/full"; - - /** Base feed url for spreadsheets. */ - private final String baseFeedUrl; - - /** - * Create a new SpreadsheetsClient. - * - * @param client The GDataClient that should be used to authenticate - * requests, retrieve feeds, etc. - * @param spreadsheetFactory The GDataParserFactory that should be used to obtain GDataParsers - * used by this client. - * @param baseFeedUrl The base URL for spreadsheets feeds. - */ - public SpreadsheetsClient(GDataClient client, - GDataParserFactory spreadsheetFactory, - String baseFeedUrl) { - super(client, spreadsheetFactory); - this.baseFeedUrl = baseFeedUrl; - } - - /** - * Create a new SpreadsheetsClient. Uses the standard base URL for spreadsheets feeds. - * - * @param client The GDataClient that should be used to authenticate - * requests, retrieve feeds, etc. - */ - public SpreadsheetsClient(GDataClient client, - GDataParserFactory spreadsheetFactory) { - this(client, spreadsheetFactory, SPREADSHEETS_BASE_FEED_URL); - } - - /* (non-Javadoc) - * @see GDataServiceClient#getServiceName - */ - public String getServiceName() { - return SERVICE; - } - - /** - * Returns a parser for the specified feed type. - * - * @param feedEntryClass the Class of entry type that will be parsed. This lets this - * method figure out which parser to create. - * @param feedUri the URI of the feed to be fetched and parsed - * @param authToken the current authToken to use for the request @return a parser for the indicated feed - * @throws HttpException if an http error is encountered - * @throws ParseException if the response from the server could not be - * parsed - */ - private GDataParser getParserForTypedFeed(Class feedEntryClass, String feedUri, - String authToken) throws ParseException, IOException, HttpException { - GDataClient gDataClient = getGDataClient(); - GDataParserFactory gDataParserFactory = getGDataParserFactory(); - - InputStream is = gDataClient.getFeedAsStream(feedUri, authToken); - return gDataParserFactory.createParser(feedEntryClass, is); - } - - /* (non-javadoc) - * @see GDataServiceClient#createEntry - */ - public Entry createEntry(String feedUri, String authToken, Entry entry) - throws ParseException, IOException, HttpException { - - GDataParserFactory factory = getGDataParserFactory(); - GDataSerializer serializer = factory.createSerializer(entry); - - InputStream is = getGDataClient().createEntry(feedUri, authToken, serializer); - GDataParser parser = factory.createParser(entry.getClass(), is); - try { - return parser.parseStandaloneEntry(); - } finally { - parser.close(); - } - } - - /** - * Returns a parser for a Cells-based feed. - * - * @param feedUri the URI of the feed to be fetched and parsed - * @param authToken the current authToken to use for the request - * @return a parser for the indicated feed - * @throws HttpException if an http error is encountered - * @throws ParseException if the response from the server could not be - * parsed - */ - public GDataParser getParserForCellsFeed(String feedUri, String authToken) - throws ParseException, IOException, HttpException { - return getParserForTypedFeed(CellEntry.class, feedUri, authToken); - } - - /** - * Fetches a GDataParser for the indicated feed. The parser can be used to - * access the contents of URI. WARNING: because we cannot reliably infer - * the feed type from the URI alone, this method assumes the default feed - * type! This is probably NOT what you want. Please use the - * getParserFor[Type]Feed methods. - * - * @param feedEntryClass - * @param feedUri the URI of the feed to be fetched and parsed - * @param authToken the current authToken to use for the request - * @return a parser for the indicated feed - * @throws HttpException if an http error is encountered - * @throws ParseException if the response from the server could not be - * parsed - */ - public GDataParser getParserForFeed(Class feedEntryClass, String feedUri, String authToken) - throws ParseException, IOException, HttpException { - GDataClient gDataClient = getGDataClient(); - GDataParserFactory gDataParserFactory = getGDataParserFactory(); - InputStream is = gDataClient.getFeedAsStream(feedUri, authToken); - return gDataParserFactory.createParser(feedEntryClass, is); - } - - /** - * Returns a parser for a List (row-based) feed. - * - * @param feedUri the URI of the feed to be fetched and parsed - * @param authToken the current authToken to use for the request - * @return a parser for the indicated feed - * @throws HttpException if an http error is encountered - * @throws ParseException if the response from the server could not be - * parsed - */ - public GDataParser getParserForListFeed(String feedUri, String authToken) - throws ParseException, IOException, HttpException { - return getParserForTypedFeed(ListEntry.class, feedUri, authToken); - } - - /** - * Returns a parser for a Spreadsheets meta-feed. - * - * @param feedUri the URI of the feed to be fetched and parsed - * @param authToken the current authToken to use for the request - * @return a parser for the indicated feed - * @throws HttpException if an http error is encountered - * @throws ParseException if the response from the server could not be - * parsed - */ - public GDataParser getParserForSpreadsheetsFeed(String feedUri, String authToken) - throws ParseException, IOException, HttpException { - return getParserForTypedFeed(SpreadsheetEntry.class, feedUri, authToken); - } - - /** - * Returns a parser for a Worksheets meta-feed. - * - * @param feedUri the URI of the feed to be fetched and parsed - * @param authToken the current authToken to use for the request - * @return a parser for the indicated feed - * @throws HttpException if an http error is encountered - * @throws ParseException if the response from the server could not be - * parsed - */ - public GDataParser getParserForWorksheetsFeed(String feedUri, String authToken) - throws ParseException, IOException, HttpException { - return getParserForTypedFeed(WorksheetEntry.class, feedUri, authToken); - } - - /** - * Updates an entry. The URI to be updated is taken from - * <code>entry</code>. Note that only entries in List and Cells feeds - * can be updated, so <code>entry</code> must be of the corresponding - * type; other types will result in an exception. - * - * @param entry the entry to be updated; must include its URI - * @param authToken the current authToken to be used for the operation - * @return An Entry containing the re-parsed version of the entry returned - * by the server in response to the update. - * @throws HttpException if an http error is encountered - * @throws ParseException if the server returned an error, if the server's - * response was unparseable (unlikely), or if <code>entry</code> - * is of a read-only type - * @throws IOException on network error - */ - public Entry updateEntry(Entry entry, String authToken) - throws ParseException, IOException, HttpException { - GDataParserFactory factory = getGDataParserFactory(); - GDataSerializer serializer = factory.createSerializer(entry); - - String editUri = entry.getEditUri(); - if (StringUtils.isEmpty(editUri)) { - throw new ParseException("No edit URI -- cannot update."); - } - - InputStream is = getGDataClient().updateEntry(editUri, authToken, serializer); - GDataParser parser = factory.createParser(entry.getClass(), is); - try { - return parser.parseStandaloneEntry(); - } finally { - parser.close(); - } - } - - public String getBaseFeedUrl() { - return baseFeedUrl; - } -} diff --git a/src/com/google/wireless/gdata/spreadsheets/data/CellEntry.java b/src/com/google/wireless/gdata/spreadsheets/data/CellEntry.java deleted file mode 100755 index 88fb6b6..0000000 --- a/src/com/google/wireless/gdata/spreadsheets/data/CellEntry.java +++ /dev/null @@ -1,134 +0,0 @@ -// 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 deleted file mode 100755 index 9281e09..0000000 --- a/src/com/google/wireless/gdata/spreadsheets/data/CellFeed.java +++ /dev/null @@ -1,35 +0,0 @@ -// 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 deleted file mode 100755 index c3e169f..0000000 --- a/src/com/google/wireless/gdata/spreadsheets/data/ListEntry.java +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2007 The Android Open Source Project -package com.google.wireless.gdata.spreadsheets.data; - -import com.google.wireless.gdata.data.Entry; -import com.google.wireless.gdata.data.StringUtils; - -import java.util.Enumeration; -import java.util.Hashtable; -import java.util.Vector; - -/** - * Represents an entry in a GData Spreadsheets List feed. - */ -public class ListEntry extends Entry { - /** Map containing the values in the row. */ - private Hashtable values = new Hashtable(); - - /** Caches the list of names, so they don't need to be recomputed. */ - private Vector names = null; - - /** - * Retrieves the column names present in this row. - * - * @return a Set of Strings, one per column where data exists - */ - public Vector getNames() { - if (names != null) { - return names; - } - names = new Vector(); - Enumeration e = values.keys(); - while (e.hasMoreElements()) { - names.add(e.nextElement()); - } - return names; - } - - /** - * Fetches the value for a column. Equivalent to - * <code>getValue(name, null)</code>. - * - * @param name the name of the column whose row is to be fetched - * @return the value of the column, or null if the column is not present - */ - public String getValue(String name) { - return getValue(name, null); - } - - /** - * Fetches the value for a column. - * - * @param name the name of the column whose row is to be fetched - * @param defaultValue the value to return if the row has no value for the - * requested column; may be null - * @return the value of the column, or null if the column is not present - */ - public String getValue(String name, String defaultValue) { - if (StringUtils.isEmpty(name)) { - return defaultValue; - } - String val = (String) values.get(name); - if (val == null) { - return defaultValue; - } - return val; - } - - /** - * Sets the value of a column. - * - * @param name the name of the column - * @param value the value for the column - */ - public void setValue(String name, String value) { - values.put(name, value == null ? "" : value); - } -} diff --git a/src/com/google/wireless/gdata/spreadsheets/data/ListFeed.java b/src/com/google/wireless/gdata/spreadsheets/data/ListFeed.java deleted file mode 100755 index d18bfa8..0000000 --- a/src/com/google/wireless/gdata/spreadsheets/data/ListFeed.java +++ /dev/null @@ -1,35 +0,0 @@ -// 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 deleted file mode 100755 index a10837b..0000000 --- a/src/com/google/wireless/gdata/spreadsheets/data/SpreadsheetEntry.java +++ /dev/null @@ -1,38 +0,0 @@ -// 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 deleted file mode 100755 index bfab289..0000000 --- a/src/com/google/wireless/gdata/spreadsheets/data/SpreadsheetFeed.java +++ /dev/null @@ -1,14 +0,0 @@ -// 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 deleted file mode 100644 index 2c00d66..0000000 --- a/src/com/google/wireless/gdata/spreadsheets/data/WorksheetEntry.java +++ /dev/null @@ -1,105 +0,0 @@ -// 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 deleted file mode 100755 index d8c469d..0000000 --- a/src/com/google/wireless/gdata/spreadsheets/data/WorksheetFeed.java +++ /dev/null @@ -1,14 +0,0 @@ -// 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/parser/xml/XmlCellsGDataParser.java b/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlCellsGDataParser.java deleted file mode 100755 index b64f76a..0000000 --- a/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlCellsGDataParser.java +++ /dev/null @@ -1,126 +0,0 @@ -// 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 deleted file mode 100755 index b582ad9..0000000 --- a/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlListGDataParser.java +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2007 The Android Open Source Project -package com.google.wireless.gdata.spreadsheets.parser.xml; - -import com.google.wireless.gdata.data.Entry; -import com.google.wireless.gdata.data.Feed; -import com.google.wireless.gdata.data.StringUtils; -import com.google.wireless.gdata.data.XmlUtils; -import com.google.wireless.gdata.parser.ParseException; -import com.google.wireless.gdata.parser.xml.XmlGDataParser; -import com.google.wireless.gdata.spreadsheets.data.ListEntry; -import com.google.wireless.gdata.spreadsheets.data.ListFeed; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.io.InputStream; - -/** - * Parser for non-Atom data in a GData Spreadsheets List-based feed. - */ -public class XmlListGDataParser extends XmlGDataParser { - /** - * The rel ID used by the server to identify the URLs for List POSTs - * (updates) - */ - private static final String LIST_FEED_POST_REL = - "http://schemas.google.com/g/2005#post"; - - /** - * Creates a new XmlListGDataParser. - * - * @param is the stream from which to read the data - * @param xmlParser the XmlPullParser to use to parse the raw XML - * @throws ParseException if the super-class throws one - */ - public XmlListGDataParser(InputStream is, XmlPullParser xmlParser) - throws ParseException { - super(is, xmlParser); - } - - /* (non-JavaDoc) - * Creates a new Entry that can handle the data parsed by this class. - */ - protected Entry createEntry() { - return new ListEntry(); - } - - /* (non-JavaDoc) - * Creates a new Feed that can handle the data parsed by this class. - */ - protected Feed createFeed() { - return new ListFeed(); - } - - /* (non-JavaDoc) - * Callback to handle non-Atom data present in an Atom entry tag. - */ - protected void handleExtraElementInEntry(Entry entry) - throws XmlPullParserException, IOException { - XmlPullParser parser = getParser(); - if (!(entry instanceof ListEntry)) { - throw new IllegalArgumentException("Expected ListEntry!"); - } - ListEntry row = (ListEntry) entry; - - String name = parser.getName(); - row.setValue(name, XmlUtils.extractChildText(parser)); - } - - /* (non-JavaDoc) - * Callback to handle non-Atom data in the feed. - */ - protected void handleExtraElementInFeed(Feed feed) - throws XmlPullParserException, IOException { - XmlPullParser parser = getParser(); - if (!(feed instanceof ListFeed)) { - throw new IllegalArgumentException("Expected ListFeed!"); - } - ListFeed listFeed = (ListFeed) feed; - - String name = parser.getName(); - if (!"link".equals(name)) { - return; - } - - // lists store column data in the gsx namespace: - // <gsx:columnheader>data</gsx:columnheader> - // The columnheader tag names are the scrubbed values of the first row. - // We extract them all and store them as keys in a Map. - int numAttrs = parser.getAttributeCount(); - String rel = null; - String href = null; - String attrName = null; - for (int i = 0; i < numAttrs; ++i) { - attrName = parser.getAttributeName(i); - if ("rel".equals(attrName)) { - rel = parser.getAttributeValue(i); - } else if ("href".equals(attrName)) { - href = parser.getAttributeValue(i); - } - } - if (!(StringUtils.isEmpty(rel) || StringUtils.isEmpty(href))) { - if (LIST_FEED_POST_REL.equals(rel)) { - listFeed.setEditUri(href); - } - } - } -} diff --git a/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlSpreadsheetsGDataParser.java b/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlSpreadsheetsGDataParser.java deleted file mode 100755 index cb1094d..0000000 --- a/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlSpreadsheetsGDataParser.java +++ /dev/null @@ -1,68 +0,0 @@ -// 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 deleted file mode 100644 index 4feed62..0000000 --- a/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlSpreadsheetsGDataParserFactory.java +++ /dev/null @@ -1,99 +0,0 @@ -// 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 deleted file mode 100755 index e9ceee5..0000000 --- a/src/com/google/wireless/gdata/spreadsheets/parser/xml/XmlWorksheetsGDataParser.java +++ /dev/null @@ -1,98 +0,0 @@ -// 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/serializer/xml/XmlCellEntryGDataSerializer.java b/src/com/google/wireless/gdata/spreadsheets/serializer/xml/XmlCellEntryGDataSerializer.java deleted file mode 100755 index 995af2c..0000000 --- a/src/com/google/wireless/gdata/spreadsheets/serializer/xml/XmlCellEntryGDataSerializer.java +++ /dev/null @@ -1,83 +0,0 @@ -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 deleted file mode 100755 index 55cc59c..0000000 --- a/src/com/google/wireless/gdata/spreadsheets/serializer/xml/XmlListEntryGDataSerializer.java +++ /dev/null @@ -1,67 +0,0 @@ -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/gdata2/ConflictDetectedException.java b/src/com/google/wireless/gdata2/ConflictDetectedException.java new file mode 100644 index 0000000..fa2d84f --- /dev/null +++ b/src/com/google/wireless/gdata2/ConflictDetectedException.java @@ -0,0 +1,32 @@ +// Copyright 2009 The Android Open Source Project. + +package com.google.wireless.gdata2; + +import com.google.wireless.gdata2.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/gdata2/GDataException.java b/src/com/google/wireless/gdata2/GDataException.java new file mode 100644 index 0000000..b5850f1 --- /dev/null +++ b/src/com/google/wireless/gdata2/GDataException.java @@ -0,0 +1,64 @@ +// Copyright 2007 The Android Open Source Project +package com.google.wireless.gdata2; + +/** + * 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/gdata2/client/AuthenticationException.java b/src/com/google/wireless/gdata2/client/AuthenticationException.java new file mode 100644 index 0000000..72caddb --- /dev/null +++ b/src/com/google/wireless/gdata2/client/AuthenticationException.java @@ -0,0 +1,37 @@ +// Copyright 2009 The Android Open Source Project. + +package com.google.wireless.gdata2.client; + +import com.google.wireless.gdata2.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/gdata2/client/BadRequestException.java b/src/com/google/wireless/gdata2/client/BadRequestException.java new file mode 100644 index 0000000..6e2e98b --- /dev/null +++ b/src/com/google/wireless/gdata2/client/BadRequestException.java @@ -0,0 +1,37 @@ +// Copyright 2009 The Android Open Source Project. + +package com.google.wireless.gdata2.client; + +import com.google.wireless.gdata2.GDataException; + +/** + * Exception thrown when the server returns a 400 Bad Request. + */ +public class BadRequestException extends GDataException { + + /** + * Creates a new AuthenticationException. + */ + public BadRequestException() { + } + + /** + * Creates a new BadRequestException with a supplied message. + * @param message The message for the exception. + */ + public BadRequestException(String message) { + super(message); + } + + /** + * Creates a new BadRequestException 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 BadRequestException(String message, Throwable cause) { + super(message, cause); + } +}
\ No newline at end of file diff --git a/src/com/google/wireless/gdata2/client/ForbiddenException.java b/src/com/google/wireless/gdata2/client/ForbiddenException.java new file mode 100644 index 0000000..49a19cb --- /dev/null +++ b/src/com/google/wireless/gdata2/client/ForbiddenException.java @@ -0,0 +1,37 @@ +// Copyright 2009 The Android Open Source Project. + +package com.google.wireless.gdata2.client; + +import com.google.wireless.gdata2.GDataException; + +/** + * Exception thrown when the server returns a 403 Forbidden. + */ +public class ForbiddenException extends GDataException { + + /** + * Creates a new AuthenticationException. + */ + public ForbiddenException() { + } + + /** + * Creates a new ForbiddenException with a supplied message. + * @param message The message for the exception. + */ + public ForbiddenException(String message) { + super(message); + } + + /** + * Creates a new ForbiddenException 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 ForbiddenException(String message, Throwable cause) { + super(message, cause); + } +}
\ No newline at end of file diff --git a/src/com/google/wireless/gdata2/client/GDataClient.java b/src/com/google/wireless/gdata2/client/GDataClient.java new file mode 100644 index 0000000..4f664c5 --- /dev/null +++ b/src/com/google/wireless/gdata2/client/GDataClient.java @@ -0,0 +1,209 @@ +// Copyright 2007 The Android Open Source Project + +package com.google.wireless.gdata2.client; + +import com.google.wireless.gdata2.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. + * @param eTag The eTag associated with this request, this will + * cause the GET to return a 304 if the content was + * not modified. The parameter can be null + * @param protocolVersion Identifies the protocol version to use + * in form of a string, e.g. "2.1". The parameter + * can not be null + * @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, + String eTag, + String protocolVersion) + 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. + * @param eTag The eTag associated with this request, this will + * cause the GET to return a 304 if the content was + * not modified. + * @param protocolVersion Identifies the protocol version to use + * in form of a string, e.g. "2.1". The parameter + * can not be null + * @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, String eTag, String protocolVersion) + throws HttpException, IOException; + + /** + * 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 protocolVersion Identifies the protocol version to use + * in form of a string, e.g. "2.1". The parameter + * can not be null + * @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, + String protocolVersion, + GDataSerializer entry) + throws HttpException, IOException; + + /** + * Connects to a GData server (specified by the editUri) and updates an + * existing entry. If the entry has a partial field mask set + * {@link setFields}, this call will be executed as a partial + * PATCH instead of a full representation PUT. 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 eTag The eTag associated with this request, this will + * cause the PUT to return a conflict if the + * resource was already modified + * @param protocolVersion Identifies the protocol version to use + * in form of a string, e.g. "2.1". The parameter + * can not be null + * @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, + String eTag, + String protocolVersion, + GDataSerializer entry) + throws HttpException, IOException; + + /** + * Connects to a GData server (specified by the editUri) and deletes an + * existing entry. Note this does not need a protocol version, + * it will default to 2.0. + * + * @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. + * @param eTag The eTag associated with this request, this will + * cause a failure if the resource was modified + * since retrieval. + * @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, + String eTag) + 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 eTag The eTag associated with this request, this will + * cause the PUT to return a conflict if the + * resource was already modified + * @param protocolVersion Identifies the protocol version to use + * in form of a string, e.g. "2.1". The parameter + * can not be null + * @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, String eTag, + String protocolVersion, InputStream mediaEntryInputStream, String contentType) + throws HttpException, IOException; + + /** + * Connects to a GData server (specified by the batchUrl) and submits a + * batch for processing. 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 batchUrl The batch url to which the batch is submitted. + * @param authToken the authentication token that should be used when + * submitting the batch. + * @param protocolVersion The version of the protocol that + * should be used for this request. + * @param batch The batch of entries to submit. + * @throws IOException Thrown if an io error occurs while communicating with + * the service. + * @throws HttpException if the service returns an error response. + */ + InputStream submitBatch(String batchUrl, + String authToken, + String protocolVersion, + GDataSerializer batch) + throws HttpException, IOException; +} diff --git a/src/com/google/wireless/gdata2/client/GDataParserFactory.java b/src/com/google/wireless/gdata2/client/GDataParserFactory.java new file mode 100644 index 0000000..f08b384 --- /dev/null +++ b/src/com/google/wireless/gdata2/client/GDataParserFactory.java @@ -0,0 +1,57 @@ +// Copyright 2007 The Android Open Source Project + +package com.google.wireless.gdata2.client; + +import com.google.wireless.gdata2.parser.GDataParser; +import com.google.wireless.gdata2.parser.ParseException; +import com.google.wireless.gdata2.data.Entry; +import com.google.wireless.gdata2.serializer.GDataSerializer; + +import java.io.InputStream; +import java.util.Enumeration; + +/** + * 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); + + /** + * Creates a new {@link GDataSerializer} for the provided batch of entries. + * + * @param batch An enumeration of entries comprising the batch. + * @return The GDataSerializer that will serialize the batch. + */ + GDataSerializer createSerializer(Enumeration batch); +} diff --git a/src/com/google/wireless/gdata2/client/GDataServiceClient.java b/src/com/google/wireless/gdata2/client/GDataServiceClient.java new file mode 100644 index 0000000..5e0aea1 --- /dev/null +++ b/src/com/google/wireless/gdata2/client/GDataServiceClient.java @@ -0,0 +1,478 @@ +// Copyright 2008 The Android Open Source Project + +package com.google.wireless.gdata2.client; + +import com.google.wireless.gdata2.ConflictDetectedException; +import com.google.wireless.gdata2.client.AuthenticationException; +import com.google.wireless.gdata2.client.HttpException; +import com.google.wireless.gdata2.client.ResourceGoneException; +import com.google.wireless.gdata2.client.ResourceNotFoundException; +import com.google.wireless.gdata2.data.Entry; +import com.google.wireless.gdata2.data.MediaEntry; +import com.google.wireless.gdata2.data.StringUtils; +import com.google.wireless.gdata2.parser.GDataParser; +import com.google.wireless.gdata2.parser.ParseException; +import com.google.wireless.gdata2.serializer.GDataSerializer; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; + +/** + * Abstract base class for service-specific clients to access GData feeds. + */ +public abstract class GDataServiceClient { + + /** + * Default gdata protocol version that this library supports + */ + protected static String DEFAULT_GDATA_VERSION = "2.0"; + + private final GDataClient gDataClient; + private final GDataParserFactory gDataParserFactory; + + public GDataServiceClient(GDataClient gDataClient, GDataParserFactory gDataParserFactory) { + this.gDataClient = gDataClient; + this.gDataParserFactory = gDataParserFactory; + } + + /** + * Returns the {@link GDataClient} being used by this GDataServiceClient. + * + * @return The {@link GDataClient} being used by this GDataServiceClient. + */ + protected GDataClient getGDataClient() { + return gDataClient; + } + + /** + * Returns the protocol version used by this GDataServiceClient, in the form + * of a "2.1" string + * + * @return String + */ + public abstract String getProtocolVersion(); + + /** + * Returns the {@link GDataParserFactory} being used by this + * GDataServiceClient. + * + * @return The {@link GDataParserFactory} being used by this + * GDataServiceClient. + */ + protected GDataParserFactory getGDataParserFactory() { + return gDataParserFactory; + } + + /** + * Returns the name of the service. Used for authentication. + * + * @return The name of the service. + */ + public abstract String getServiceName(); + + /** + * Creates {@link QueryParams} that can be used to restrict the feed contents + * that are fetched. + * + * @return The QueryParams that can be used with this client. + */ + public QueryParams createQueryParams() { + return gDataClient.createQueryParams(); + } + + /** + * Fetches a feed for this user. The caller is responsible for closing the + * returned {@link GDataParser}. + * + * @param feedEntryClass the class of Entry that is contained in the feed + * @param feedUrl ThAe URL of the feed that should be fetched. + * @param authToken The authentication token for this user. + * @param eTag The etag used for this query. Passing null will result in an + * unconditional query + * @return A {@link GDataParser} for the requested feed. + * @throws AuthenticationException Thrown if the server considers the + * authToken invalid. + * @throws ResourceGoneException Thrown if the server indicates that the + * resource is Gone. Currently used to indicate that some Tombstones + * are missing. + * @throws ResourceNotModifiedException Thrown if the retrieval fails because + * the specified ETag matches the current ETag of the entry (i.e. the + * entry has not been modified since last retrieval). + * @throws HttpException Thrown if the request returns an error response. + * @throws ParseException Thrown if the server response cannot be parsed. + * @throws IOException Thrown if an error occurs while communicating with the + * GData service. + */ + public GDataParser getParserForFeed(Class feedEntryClass, String feedUrl, String authToken, + String eTag) throws AuthenticationException, ResourceGoneException, + ResourceNotModifiedException, HttpException, ParseException, IOException, + ForbiddenException { + try { + InputStream is = gDataClient.getFeedAsStream(feedUrl, authToken, eTag, getProtocolVersion()); + return gDataParserFactory.createParser(feedEntryClass, is); + } catch (HttpException e) { + convertHttpExceptionForFeedReads("Could not fetch feed " + feedUrl, 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. + * @param eTag The ETag associated with this request. + * @return A {@link InputStream} for the requested media entry. + * @throws AuthenticationException Thrown if the server considers the + * authToken invalid. + * @throws ResourceGoneException Thrown if the server indicates that the + * resource is Gone. Currently used to indicate that some Tombstones + * are missing. + * @throws ResourceNotModifiedException Thrown if the retrieval fails because + * the specified ETag matches the current ETag of the entry (i.e. the + * entry has not been modified since last retrieval). + * @throws ResourceNotFoundException Thrown if the resource was not found. + * @throws HttpException Thrown if the request returns an error response. + * @throws IOException Thrown if an error occurs while communicating with the + * GData service. + */ + public InputStream getMediaEntryAsStream(String mediaEntryUrl, String authToken, String eTag) + throws AuthenticationException, ResourceGoneException, ResourceNotModifiedException, + ResourceNotFoundException, HttpException, IOException, ForbiddenException { + try { + return gDataClient + .getMediaEntryAsStream(mediaEntryUrl, authToken, eTag, getProtocolVersion()); + } catch (HttpException e) { + convertHttpExceptionForEntryReads("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 ConflictDetectedException Thrown if the server detects an existing + * entry that conflicts with this one. + * @throws AuthenticationException Thrown if the server considers the + * authToken invalid. + * @throws PreconditionFailedException Thrown if the update fails because the + * specified ETag does not match the current ETag of the + * @throws HttpException Thrown if the request returns an error response. + * @throws ParseException Thrown if the server response cannot be parsed. + * @throws IOException Thrown if an error occurs while communicating with the + * GData service. + * @throws BadRequestException thrown if the server returns a 400 + * @throws ForbiddenException thrown if the server returns a 403 + */ + public Entry createEntry(String feedUrl, String authToken, Entry entry) + throws ConflictDetectedException, AuthenticationException, PreconditionFailedException, + HttpException, ParseException, IOException, ForbiddenException, BadRequestException { + GDataSerializer serializer = gDataParserFactory.createSerializer(entry); + try { + InputStream is = + gDataClient.createEntry(feedUrl, authToken, getProtocolVersion(), serializer); + return parseEntry(entry.getClass(), is); + } catch (HttpException e) { + try { + convertHttpExceptionForWrites(entry.getClass(), + "Could not create " + "entry " + feedUrl, e); + } catch (ResourceNotFoundException e1) { + // this should never happen + throw 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 + * @param eTag The etag used for this query. Passing null will result in an + * unconditional query + * @return The entry returned by the server. + * @throws AuthenticationException Thrown if the server considers the + * authToken invalid. + * @throws ResourceNotFoundException Thrown if the resource was not found. + * @throws ResourceNotModifiedException Thrown if the retrieval fails because + * the specified ETag matches the current ETag of the entry (i.e. the + * entry has not been modified since last retrieval). + * @throws HttpException Thrown if the request returns an error response. + * @throws ParseException Thrown if the server response cannot be parsed. + * @throws IOException Thrown if an error occurs while communicating with the + * GData service. + */ + public Entry getEntry(Class entryClass, String id, String authToken, String eTag) + throws AuthenticationException, ResourceNotFoundException, ResourceNotModifiedException, + HttpException, ParseException, IOException, ForbiddenException { + try { + InputStream is = getGDataClient().getFeedAsStream(id, authToken, eTag, getProtocolVersion()); + return parseEntry(entryClass, is); + } catch (HttpException e) { + convertHttpExceptionForEntryReads("Could not fetch entry " + id, 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 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. + * @throws PreconditionFailedException Thrown if the update fails because the + * specified ETag does not match the current ETag of the entry. + * @throws HttpException Thrown if the request returns an error response. + * @throws ParseException Thrown if the server response cannot be parsed. + * @throws IOException Thrown if an error occurs while communicating with the + * GData service. + * @throws BadRequestException thrown if the server returns a 400 + * @throws ForbiddenException thrown if the server returns a 403 + * @throws ResourceNotFoundException Thrown if the resource was not found. + */ + public Entry updateEntry(Entry entry, String authToken) throws AuthenticationException, + ConflictDetectedException, PreconditionFailedException, HttpException, ParseException, + IOException, ForbiddenException, ResourceNotFoundException, BadRequestException { + 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, entry.getETag(), getProtocolVersion(), + serializer); + return parseEntry(entry.getClass(), is); + } catch (HttpException e) { + convertHttpExceptionForWrites(entry.getClass(), "Could not update " + "entry " + editUri, 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 + * @param eTag The etag used for this query. Passing null will result in an + * unconditional query + * @throws AuthenticationException Thrown if the server considers the + * authToken invalid. + * @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. + * @throws PreconditionFailedException Thrown if the update fails because the + * specified ETag does not match the current ETag of the entry. + * @throws HttpException Thrown if the request returns an error response. + * @throws ParseException Thrown if the server response cannot be parsed. + * @throws IOException Thrown if an error occurs while communicating with the + * GData service. + * @throws BadRequestException thrown if the server returns a 400 + * @throws ForbiddenException thrown if the server returns a 403 + * @throws ResourceNotFoundException Thrown if the resource was not found. + */ + public MediaEntry updateMediaEntry(String editUri, InputStream inputStream, String contentType, + String authToken, String eTag) throws AuthenticationException, ConflictDetectedException, + PreconditionFailedException, HttpException, ParseException, IOException, + ForbiddenException, ResourceNotFoundException, BadRequestException { + if (StringUtils.isEmpty(editUri)) { + throw new IllegalArgumentException("No edit URI -- cannot update."); + } + + try { + InputStream is = + gDataClient.updateMediaEntry(editUri, authToken, eTag, getProtocolVersion(), inputStream, + contentType); + return (MediaEntry) parseEntry(MediaEntry.class, is); + } catch (HttpException e) { + convertHttpExceptionForWrites(MediaEntry.class, "Could not update entry " + editUri, 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. + * @param eTag The etag used for this query. Passing null will result in an + * unconditional query + * @throws AuthenticationException Thrown if the server considers the + * authToken invalid. + * @throws ConflictDetectedException Thrown if the server version of this + * entry has changed since it was retrieved. + * @throws PreconditionFailedException Thrown if the update fails because the + * specified ETag does not match the current ETag of the entry. + * @throws HttpException Thrown if the request returns an error response. + * @throws ParseException Thrown if the server response cannot be parsed. + * @throws IOException Thrown if an error occurs while communicating with the + * GData service. + * @throws BadRequestException thrown if the server returns a 400 + * @throws ForbiddenException thrown if the server returns a 403 + * @throws ResourceNotFoundException Thrown if the resource was not found. + */ + public void deleteEntry(String editUri, String authToken, String eTag) + throws AuthenticationException, ConflictDetectedException, PreconditionFailedException, + HttpException, ParseException, IOException, ForbiddenException, + ResourceNotFoundException, BadRequestException { + try { + gDataClient.deleteEntry(editUri, authToken, eTag); + } 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 " + editUri, 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(); + } + } + } + + /** + * Submits a batch of operations. + * + * @param feedEntryClass the type of the entry to expect. + * @param batchUrl The url to which the batch is submitted. + * @param authToken The authentication token for this user. + * @param entries an enumeration of the entries to submit. + * @throws AuthenticationException Thrown if the server considers the + * authToken invalid. + * @throws HttpException if the service returns an error response + * @throws ParseException Thrown if the server response cannot be parsed. + * @throws IOException Thrown if an error occurs while communicating with the + * GData service. + * @throws BadRequestException thrown if the server returns a 400 + * @throws ForbiddenException thrown if the server returns a 403 + */ + public GDataParser submitBatch(Class feedEntryClass, String batchUrl, String authToken, + Enumeration entries) throws AuthenticationException, HttpException, ParseException, + IOException, ForbiddenException, BadRequestException { + GDataSerializer serializer = gDataParserFactory.createSerializer(entries); + try { + InputStream is = + gDataClient.submitBatch(batchUrl, authToken, getProtocolVersion(), serializer); + return gDataParserFactory.createParser(feedEntryClass, is); + } catch (HttpException e) { + convertHttpExceptionsForBatches("Could not submit batch " + batchUrl, e); + return null; // never reached. + } + } + + protected void convertHttpExceptionForFeedReads(String message, HttpException cause) + throws AuthenticationException, ResourceGoneException, ResourceNotModifiedException, + HttpException, ForbiddenException { + switch (cause.getStatusCode()) { + case HttpException.SC_FORBIDDEN: + throw new ForbiddenException(message, cause); + case HttpException.SC_UNAUTHORIZED: + throw new AuthenticationException(message, cause); + case HttpException.SC_GONE: + throw new ResourceGoneException(message, cause); + case HttpException.SC_NOT_MODIFIED: + throw new ResourceNotModifiedException(message, cause); + default: + throw new HttpException(message + ": " + cause.getMessage(), cause.getStatusCode(), cause + .getResponseStream()); + } + } + + protected void convertHttpExceptionForEntryReads(String message, HttpException cause) + throws AuthenticationException, HttpException, ResourceNotFoundException, + ResourceNotModifiedException, ForbiddenException { + switch (cause.getStatusCode()) { + case HttpException.SC_FORBIDDEN: + throw new ForbiddenException(message, cause); + case HttpException.SC_UNAUTHORIZED: + throw new AuthenticationException(message, cause); + case HttpException.SC_NOT_FOUND: + throw new ResourceNotFoundException(message, cause); + case HttpException.SC_NOT_MODIFIED: + throw new ResourceNotModifiedException(message, cause); + default: + throw new HttpException(message + ": " + cause.getMessage(), cause.getStatusCode(), cause + .getResponseStream()); + } + } + + protected void convertHttpExceptionsForBatches(String message, HttpException cause) + throws AuthenticationException, ParseException, HttpException, ForbiddenException, + BadRequestException { + switch (cause.getStatusCode()) { + case HttpException.SC_FORBIDDEN: + throw new ForbiddenException(message, cause); + case HttpException.SC_UNAUTHORIZED: + throw new AuthenticationException(message, cause); + case HttpException.SC_BAD_REQUEST: + throw new BadRequestException(message, cause); + default: + throw new HttpException(message + ": " + cause.getMessage(), cause.getStatusCode(), cause + .getResponseStream()); + } + } + + protected void convertHttpExceptionForWrites(Class entryClass, String message, + HttpException cause) + throws ConflictDetectedException, AuthenticationException, PreconditionFailedException, + ParseException, HttpException, IOException, ForbiddenException, + ResourceNotFoundException, BadRequestException { + switch (cause.getStatusCode()) { + case HttpException.SC_CONFLICT: + Entry entry = null; + 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 BadRequestException(message, cause); + case HttpException.SC_FORBIDDEN: + throw new ForbiddenException(message, cause); + case HttpException.SC_UNAUTHORIZED: + throw new AuthenticationException(message, cause); + case HttpException.SC_PRECONDITION_FAILED: + throw new PreconditionFailedException(message, cause); + case HttpException.SC_NOT_FOUND: + throw new ResourceNotFoundException(message, cause); + default: + throw new HttpException(message + ": " + cause.getMessage(), cause.getStatusCode(), cause + .getResponseStream()); + } + } +} diff --git a/src/com/google/wireless/gdata2/client/HttpException.java b/src/com/google/wireless/gdata2/client/HttpException.java new file mode 100644 index 0000000..4ca71b7 --- /dev/null +++ b/src/com/google/wireless/gdata2/client/HttpException.java @@ -0,0 +1,63 @@ +// Copyright 2007 The Android Open Source Project + +package com.google.wireless.gdata2.client; + +import com.google.wireless.gdata2.GDataException; + +import java.io.InputStream; + +/** + * A class representing exceptional (i.e., non 200) responses from an HTTP + * Server. + */ +public class HttpException extends GDataException { + + public static final int SC_NOT_MODIFIED = 304; + + 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_PRECONDITION_FAILED = 412; + + 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. + */ + 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/gdata2/client/HttpQueryParams.java b/src/com/google/wireless/gdata2/client/HttpQueryParams.java new file mode 100644 index 0000000..bfd9eaf --- /dev/null +++ b/src/com/google/wireless/gdata2/client/HttpQueryParams.java @@ -0,0 +1,73 @@ +// Copyright 2007 The Android Open Source Project + +package com.google.wireless.gdata2.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/gdata2/client/PreconditionFailedException.java b/src/com/google/wireless/gdata2/client/PreconditionFailedException.java new file mode 100644 index 0000000..caa880e --- /dev/null +++ b/src/com/google/wireless/gdata2/client/PreconditionFailedException.java @@ -0,0 +1,39 @@ +// Copyright 2009 The Android Open Source Project. + +package com.google.wireless.gdata2.client; + +import com.google.wireless.gdata2.GDataException; + +/** + * Exception thrown when an update fails because the specified ETag doesn't + * match the current ETag on the entry (which implies that the entry has changed + * on the server since it was last retrieved). + */ +public class PreconditionFailedException extends GDataException { + + /** + * Creates a new PreconditionFailedException. + */ + public PreconditionFailedException() { + } + + /** + * Creates a new PreconditionFailedException with a supplied message. + * @param message The message for the exception. + */ + public PreconditionFailedException(String message) { + super(message); + } + + /** + * Creates a new PreconditionFailedException 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 PreconditionFailedException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/com/google/wireless/gdata2/client/QueryParams.java b/src/com/google/wireless/gdata2/client/QueryParams.java new file mode 100644 index 0000000..c3afb4a --- /dev/null +++ b/src/com/google/wireless/gdata2/client/QueryParams.java @@ -0,0 +1,258 @@ +// Copyright 2007 The Android Open Source Project + +package com.google.wireless.gdata2.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"; + + /** + * Param name constant for the fields used in partial retrievals + */ + public static final String FIELDS_PARAM = "fields"; + + 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 int getMaxResults() { + return Integer.parseInt(getParamValue(MAX_RESULTS_PARAM)); + } + + /** + * @param maxResults the maxResults to set + */ + public void setMaxResults(int maxResults) { + setParamValue(MAX_RESULTS_PARAM, String.valueOf(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 int getStartIndex() { + return Integer.parseInt(getParamValue(START_INDEX_PARAM)); + } + + /** + * @param startIndex the startIndex to set + */ + public void setStartIndex(int startIndex) { + setParamValue(START_INDEX_PARAM, String.valueOf(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); + } + + /** + * @return the field list used + */ + public String getFields() { + return getParamValue(FIELDS_PARAM); + } + + /** + * @param fields the fields expression to be used + */ + public void setFields(String fields) { + setParamValue(FIELDS_PARAM, fields); + } +} diff --git a/src/com/google/wireless/gdata2/client/ResourceGoneException.java b/src/com/google/wireless/gdata2/client/ResourceGoneException.java new file mode 100644 index 0000000..c9911da --- /dev/null +++ b/src/com/google/wireless/gdata2/client/ResourceGoneException.java @@ -0,0 +1,37 @@ +// Copyright 2009 The Android Open Source Project. + +package com.google.wireless.gdata2.client; + +import com.google.wireless.gdata2.GDataException; + +/** + * Exception thrown when a specified resource is gone + */ +public class ResourceGoneException extends GDataException { + + /** + * Creates a new ResourceGoneException. + */ + public ResourceGoneException() { + } + + /** + * Creates a new ResourceGoneException with a supplied message. + * @param message The message for the exception. + */ + public ResourceGoneException(String message) { + super(message); + } + + /** + * Creates a new ResourceGoneException 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 ResourceGoneException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/com/google/wireless/gdata2/client/ResourceNotFoundException.java b/src/com/google/wireless/gdata2/client/ResourceNotFoundException.java new file mode 100644 index 0000000..766abd6 --- /dev/null +++ b/src/com/google/wireless/gdata2/client/ResourceNotFoundException.java @@ -0,0 +1,37 @@ +// Copyright 2009 The Android Open Source Project. + +package com.google.wireless.gdata2.client; + +import com.google.wireless.gdata2.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/gdata2/client/ResourceNotModifiedException.java b/src/com/google/wireless/gdata2/client/ResourceNotModifiedException.java new file mode 100644 index 0000000..2873bde --- /dev/null +++ b/src/com/google/wireless/gdata2/client/ResourceNotModifiedException.java @@ -0,0 +1,39 @@ +// Copyright 2009 The Android Open Source Project. + +package com.google.wireless.gdata2.client; + +import com.google.wireless.gdata2.GDataException; + +/** + * Exception thrown when a retrieval fails because the specified ETag matches + * the current ETag on the entry (which implies that the entry has not changed + * on the server since it was last retrieved). + */ +public class ResourceNotModifiedException extends GDataException { + + /** + * Creates a new ResourceNotModifiedException. + */ + public ResourceNotModifiedException() { + } + + /** + * Creates a new ResourceNotModifiedException with a supplied message. + * @param message The message for the exception. + */ + public ResourceNotModifiedException(String message) { + super(message); + } + + /** + * Creates a new ResourceNotModifiedException 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 ResourceNotModifiedException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/com/google/wireless/gdata/spreadsheets/client/package.html b/src/com/google/wireless/gdata2/client/package.html index 1c9bf9d..1c9bf9d 100644 --- a/src/com/google/wireless/gdata/spreadsheets/client/package.html +++ b/src/com/google/wireless/gdata2/client/package.html diff --git a/src/com/google/wireless/gdata2/contacts/client/ContactsClient.java b/src/com/google/wireless/gdata2/contacts/client/ContactsClient.java new file mode 100644 index 0000000..f0258c8 --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/client/ContactsClient.java @@ -0,0 +1,43 @@ +// Copyright 2007 The Android Open Source Project + +package com.google.wireless.gdata2.contacts.client; + +import com.google.wireless.gdata2.client.GDataClient; +import com.google.wireless.gdata2.client.GDataParserFactory; +import com.google.wireless.gdata2.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; + } + + /** + * Returns the protocol version used by this GDataServiceClient, + * in the form of a string. Contacts is using "3.0" + * + * @return the protocolVersion + */ + public String getProtocolVersion() { + return "3.0"; + } +} diff --git a/src/com/google/wireless/gdata/spreadsheets/data/package.html b/src/com/google/wireless/gdata2/contacts/client/package.html index 1c9bf9d..1c9bf9d 100644 --- a/src/com/google/wireless/gdata/spreadsheets/data/package.html +++ b/src/com/google/wireless/gdata2/contacts/client/package.html diff --git a/src/com/google/wireless/gdata2/contacts/data/CalendarLink.java b/src/com/google/wireless/gdata2/contacts/data/CalendarLink.java new file mode 100644 index 0000000..71fef94 --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/data/CalendarLink.java @@ -0,0 +1,54 @@ +// Copyright 2009 The Android Open Source Project + +package com.google.wireless.gdata2.contacts.data; + +import com.google.wireless.gdata2.data.StringUtils; + +/** + * Storage for URL of the contact's calendar. The element can be + * repeated. + */ +public class CalendarLink extends ContactsElement { + /** The phone number type. */ + public static final byte TYPE_HOME = 1; + public static final byte TYPE_WORK = 2; + public static final byte TYPE_FREE_BUSY = 3; + + /** + * default empty constructor + */ + public CalendarLink() {} + + /** + * constructor that allows initialization + */ + public CalendarLink(String href, byte type, String label, boolean isPrimary) { + super(type, label, isPrimary); + setHRef(href); + } + + private String href; + + /** + * The URL of the calendar. + */ + public String getHRef() { + return this.href; + } + + /** + * The URL of the calendar. + */ + public void setHRef(String href) { + this.href = href; + } + + + public void toString(StringBuffer sb) { + sb.append("CalendarLink"); + super.toString(sb); + if (!StringUtils.isEmpty(href)) { + sb.append(" href:").append(href); + } + } +} diff --git a/src/com/google/wireless/gdata2/contacts/data/ContactEntry.java b/src/com/google/wireless/gdata2/contacts/data/ContactEntry.java new file mode 100644 index 0000000..00d7b4c --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/data/ContactEntry.java @@ -0,0 +1,720 @@ +// Copyright 2009 The Android Open Source Project + +package com.google.wireless.gdata2.contacts.data; + +import java.util.Enumeration; +import java.util.Vector; + +import com.google.wireless.gdata2.data.Entry; +import com.google.wireless.gdata2.data.ExtendedProperty; +import com.google.wireless.gdata2.data.StringUtils; +import com.google.wireless.gdata2.parser.ParseException; + +/** + * Entry containing information about a contact. + */ +public class ContactEntry extends Entry { + private String linkPhotoHref; + private String linkPhotoType; + private String linkPhotoEtag; + 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(); + + // new collections in Contacts v3 + private final Vector calendarLinks = new Vector(); + private final Vector events = new Vector(); + private final Vector externalIds = new Vector(); + private final Vector hobbies = new Vector(); + private final Vector jots = new Vector(); + private final Vector languages = new Vector(); + private final Vector relations = new Vector(); + private final Vector userDefinedFields = new Vector(); + private final Vector webSites = new Vector(); + + // new properties in contacts v3 + private String directoryServer; + private String gender; + private String initials; + private String maidenName; + private String mileage; + private String nickname; + private String occupation; + private String shortName; + private String subject; + private String birthday; + private String billingInformation; + + public static final String GENDER_MALE = "male"; + public static final String GENDER_FEMALE = "female"; + public static final byte TYPE_PRIORITY_HIGH = 1; + public static final byte TYPE_PRIORITY_NORMAL = 2; + public static final byte TYPE_PRIORITY_LOW = 3; + private byte priority = TypedElement.TYPE_NONE; + + public static final byte TYPE_SENSITIVITY_CONFIDENTIAL = 1; + public static final byte TYPE_SENSITIVITY_NORMAL = 2; + public static final byte TYPE_SENSITIVITY_PERSONAL = 3; + public static final byte TYPE_SENSITIVITY_PRIVATE = 4; + private byte sensitivity = TypedElement.TYPE_NONE; + + private Name name; + + /** + * default empty constructor + */ + public ContactEntry() { + super(); + } + + public void setLinkPhoto(String href, String type, String photoEtag) { + this.linkPhotoHref = href; + this.linkPhotoType = type; + this.linkPhotoEtag = photoEtag; + } + + public String getLinkPhotoETag() { + return linkPhotoEtag; + } + + 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(StructuredPostalAddress 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; + } + + /** + * Accessor to the CalendarLink Collection + */ + public Vector getCalendarLinks() { + return calendarLinks; + } + + /** + * Adds a new member to the CalendarLink collection + */ + public void addCalendarLink(CalendarLink calendarLink) { + calendarLinks.addElement(calendarLink); + } + + /** + * Accessor to the Event Collection + */ + public Vector getEvents() { + return events; + } + + /** + * Adds a new member to the Event collection + */ + public void addEvent(Event event) { + events.addElement(event); + } + + + + /** + * Accessor to the ExternalId Collection + */ + public Vector getExternalIds() { + return externalIds; + } + + /** + * Adds a new member to the ExternalId collection + */ + public void addExternalId(ExternalId externalId) { + externalIds.addElement(externalId); + } + + /** + * Accessor to the Hobbies Collection + */ + public Vector getHobbies() { + return hobbies; + } + + /** + * Adds a new member to the Hobbies collection + */ + public void addHobby(String hobby) { + hobbies.addElement(hobby); + } + + /** + * Accessor to the Jots Collection + */ + public Vector getJots() { + return jots; + } + + /** + * Adds a new member to the Jot collection + */ + public void addJot(Jot jot) { + jots.addElement(jot); + } + + /** + * Accessor to the Language Collection + */ + public Vector getLanguages() { + return languages; + } + + /** + * Adds a new member to the Language collection + */ + public void addLanguage(Language language) { + languages.addElement(language); + } + + /** + * Accessor to the Relation Collection + */ + public Vector getRelations() { + return relations; + } + + /** + * Adds a new member to the Relation collection + */ + public void addRelation(Relation relation) { + relations.addElement(relation); + } + + /** + * Accessor to the UserDefinedField Collection + */ + public Vector getUserDefinedFields() { + return userDefinedFields; + } + + /** + * Adds a new member to the UserDefinedField collection + */ + public void addUserDefinedField(UserDefinedField userDefinedField) { + userDefinedFields.addElement(userDefinedField); + } + + /** + * Accessor to the WebSite Collection + */ + public Vector getWebSites() { + return webSites; + } + + /** + * Adds a new member to the WebSite collection + */ + public void addWebSite(WebSite webSite) { + webSites.addElement(webSite); + } + + + /** + * Directory server associated with the contact + */ + public String getDirectoryServer() { + return this.directoryServer; + } + /** + * Directory server associated with the contact + */ + public void setDirectoryServer(String directoryServer) { + this.directoryServer = directoryServer; + } + + /** + * Gender associated with the contact. + */ + public String getGender() { + return this.gender; + } + + /** + * Gender associated with the contact. + */ + public void setGender(String gender) { + this.gender = gender; + } + + /** + * Contact's initials. + */ + public String getInitials() { + return this.initials; + } + + /** + * Contact's initials. + */ + public void setInitials(String initials) { + this.initials = initials; + } + + /** + * Maiden name associated with the contact. + */ + public String getMaidenName() { + return this.maidenName; + } + + /** + * Maiden name associated with the contact. + */ + public void setMaidenName(String maidenName) { + this.maidenName = maidenName; + } + + /** + * Mileage associated with the contact. + */ + public String getMileage() { + return this.mileage; + } + + /** + * Mileage associated with the contact. + */ + public void setMileage(String mileage) { + this.mileage = mileage; + } + + /** + * Nickname associated with this Contact + */ + public String getNickname() { + return this.nickname; + } + + /** + * Nickname associated with this Contact + */ + public void setNickname(String nickname) { + this.nickname = nickname; + } + + /** + * Occupation associated with this Contact + */ + public String getOccupation() { + return this.occupation; + } + + /** + * Occupation associated with this Contact + */ + public void setOccupation(String occupation) { + this.occupation = occupation; + } + + /** + * Priority associated with this Contact + */ + public byte getPriority() { + return this.priority; + } + + /** + * Priority associated with this Contact + */ + public void setPriority(byte priority) { + this.priority = priority; + } + + /** + * Specifies contact's sensitivity. Can be either confidential, + * normal, personal or private. + */ + public byte getSensitivity() { + return this.sensitivity; + } + + /** + * Specifies contact's sensitivity. Can be either confidential, + * normal, personal or private. + */ + public void setSensitivity(byte sensitiviy) { + this.sensitivity = sensitiviy; + } + + /** + * ShortName associated with this Contact + */ + public String getShortName() { + return this.shortName; + } + + /** + * ShortName associated with this Contact + */ + public void setShortName(String shortName) { + this.shortName = shortName; + } + + /** + * Subject associated with this Contact + */ + public String getSubject() { + return this.subject; + } + + /** + * Subject associated with this Contact + */ + public void setSubject(String subject) { + this.subject = subject; + } + + /** + * Name associated with this Contact + */ + public Name getName() { + return this.name; + } + + /** + * Name associated with this Contact + */ + public void setName(Name name) { + this.name = name; + } + + /** + * Birthday associated with this Contact + */ + public String getBirthday() { + return this.birthday; + } + + /** + * Birthday associated with this Contact + */ + public void setBirthday(String birthday) { + this.birthday = birthday; + } + + /** + * BillingInformation associated with this Contact + */ + public String getBillingInformation() { + return this.billingInformation; + } + + /** + * BillingInformation associated with this Contact + */ + public void setBillingInformation(String billingInformation) { + this.billingInformation = billingInformation; + } + + /* + * (non-Javadoc) + * @see com.google.wireless.gdata2.data.Entry#clear() + */ + public void clear() { + super.clear(); + linkPhotoHref = null; + linkPhotoType = null; + linkPhotoEtag = null; + directoryServer = null; + gender = null; + initials = null; + maidenName = null; + mileage = null; + nickname = null; + occupation = null; + priority = TypedElement.TYPE_NONE; + sensitivity = TypedElement.TYPE_NONE; + shortName = null; + subject = null; + birthday = null; + billingInformation = null; + name = null; + emailAddresses.removeAllElements(); + imAddresses.removeAllElements(); + phoneNumbers.removeAllElements(); + postalAddresses.removeAllElements(); + organizations.removeAllElements(); + extendedProperties.removeAllElements(); + groups.removeAllElements(); + calendarLinks.removeAllElements(); + events.removeAllElements(); + externalIds.removeAllElements(); + hobbies.removeAllElements(); + jots.removeAllElements(); + languages.removeAllElements(); + relations.removeAllElements(); + userDefinedFields.removeAllElements(); + webSites.removeAllElements(); + + } + + protected void toString(StringBuffer sb) { + super.toString(sb); + sb.append("\n"); + sb.append("ContactEntry:"); + if (!StringUtils.isEmpty(linkPhotoHref)) { + sb.append(" linkPhotoHref:").append(linkPhotoHref); + } + if (!StringUtils.isEmpty(linkPhotoType)) { + sb.append(" linkPhotoType:").append(linkPhotoType); + } + if (!StringUtils.isEmpty(linkPhotoEtag)) { + sb.append(" linkPhotoEtag:").append(linkPhotoEtag); + } + if (!StringUtils.isEmpty(directoryServer)) { + sb.append(" directoryServer:").append(directoryServer); + } + if (!StringUtils.isEmpty(gender)) { + sb.append(" gender:").append(gender); + } + if (!StringUtils.isEmpty(initials)) { + sb.append(" initials:").append(initials); + } + if (!StringUtils.isEmpty(maidenName)) { + sb.append(" maidenName:").append(maidenName); + } + if (!StringUtils.isEmpty(mileage)) { + sb.append(" mileage:").append(mileage); + } + if (!StringUtils.isEmpty(nickname)) { + sb.append(" nickname:").append(nickname); + } + if (!StringUtils.isEmpty(occupation)) { + sb.append(" occupaton:").append(occupation); + } + sb.append(" priority:").append(priority); + + sb.append(" sensitivity:").append(sensitivity); + + if (!StringUtils.isEmpty(shortName)) { + sb.append(" shortName:").append(shortName); + } + if (!StringUtils.isEmpty(subject)) { + sb.append(" subject:").append(subject); + } + if (!StringUtils.isEmpty(birthday)) { + sb.append(" birthday:").append(birthday); + } + if (!StringUtils.isEmpty(billingInformation)) { + sb.append(" billingInformation:").append(billingInformation); + } + sb.append("\n"); + if (name != null) { + name.toString(sb); + sb.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(" "); + ((StructuredPostalAddress) 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"); + } + for (Enumeration iter = calendarLinks.elements(); + iter.hasMoreElements(); ) { + sb.append(" "); + ((CalendarLink) iter.nextElement()).toString(sb); + sb.append("\n"); + } + for (Enumeration iter = events.elements(); + iter.hasMoreElements(); ) { + sb.append(" "); + ((Event) iter.nextElement()).toString(sb); + sb.append("\n"); + } + for (Enumeration iter = externalIds.elements(); + iter.hasMoreElements(); ) { + sb.append(" "); + ((ExternalId) iter.nextElement()).toString(sb); + sb.append("\n"); + } + for (Enumeration iter = hobbies.elements(); + iter.hasMoreElements(); ) { + sb.append(" "); + sb.append ((String) iter.nextElement()); + sb.append("\n"); + } + for (Enumeration iter = jots.elements(); + iter.hasMoreElements(); ) { + sb.append(" "); + sb.append ((Jot) iter.nextElement()); + sb.append("\n"); + } + for (Enumeration iter = languages.elements(); + iter.hasMoreElements(); ) { + sb.append(" "); + ((Language) iter.nextElement()).toString(sb); + sb.append("\n"); + } + for (Enumeration iter = relations.elements(); + iter.hasMoreElements(); ) { + sb.append(" "); + ((Relation) iter.nextElement()).toString(sb); + sb.append("\n"); + } + for (Enumeration iter = userDefinedFields.elements(); + iter.hasMoreElements(); ) { + sb.append(" "); + ((UserDefinedField) iter.nextElement()).toString(sb); + sb.append("\n"); + } + for (Enumeration iter = webSites.elements(); + iter.hasMoreElements(); ) { + sb.append(" "); + ((WebSite) iter.nextElement()).toString(sb); + sb.append("\n"); + } + } + + public void validate() throws ParseException { + super.validate(); + if (gender != null && !GENDER_FEMALE.equals(gender) && !GENDER_MALE.equals(gender)) { + throw new ParseException( + String.format("invalid gender \"%s\", must be one of \"%s\" or \"%s\"", + gender, GENDER_FEMALE, GENDER_MALE)); + } + 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(); ) { + ((StructuredPostalAddress) 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(); + } + + for (Enumeration iter = calendarLinks.elements(); iter.hasMoreElements(); ) { + ((CalendarLink) iter.nextElement()).validate(); + } + for (Enumeration iter = events.elements(); iter.hasMoreElements(); ) { + ((Event) iter.nextElement()).validate(); + } + for (Enumeration iter = externalIds.elements(); iter.hasMoreElements(); ) { + ((ExternalId) iter.nextElement()).validate(); + } + for (Enumeration iter = languages.elements(); iter.hasMoreElements(); ) { + ((Language) iter.nextElement()).validate(); + } + for (Enumeration iter = relations.elements(); iter.hasMoreElements(); ) { + ((Relation) iter.nextElement()).validate(); + } + for (Enumeration iter = userDefinedFields.elements(); iter.hasMoreElements(); ) { + ((UserDefinedField) iter.nextElement()).validate(); + } + for (Enumeration iter = webSites.elements(); iter.hasMoreElements(); ) { + ((WebSite) iter.nextElement()).validate(); + } + } +} diff --git a/src/com/google/wireless/gdata2/contacts/data/ContactsElement.java b/src/com/google/wireless/gdata2/contacts/data/ContactsElement.java new file mode 100644 index 0000000..d8e23cc --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/data/ContactsElement.java @@ -0,0 +1,33 @@ +/** + * Copyright (C) 2009 The Android Open Source Project + */ +package com.google.wireless.gdata2.contacts.data; + +import com.google.wireless.gdata2.parser.ParseException; + + +/** + * Contains attributes that are common to all elements in a ContactEntry. + */ +public abstract class ContactsElement extends TypedElement { + private boolean isPrimary; + + public ContactsElement() {} + public ContactsElement(byte type, String label, boolean isPrimary) { + super(type, label); + this.isPrimary = isPrimary; + } + + public boolean isPrimary() { + return isPrimary; + } + + public void setIsPrimary(boolean primary) { + isPrimary = primary; + } + + public void toString(StringBuffer sb) { + super.toString(sb); + sb.append(" isPrimary:").append(isPrimary); + } +} diff --git a/src/com/google/wireless/gdata2/contacts/data/ContactsFeed.java b/src/com/google/wireless/gdata2/contacts/data/ContactsFeed.java new file mode 100644 index 0000000..1e879f0 --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/data/ContactsFeed.java @@ -0,0 +1,16 @@ +// Copyright 2007 The Android Open Source Project + +package com.google.wireless.gdata2.contacts.data; + +import com.google.wireless.gdata2.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/gdata2/contacts/data/EmailAddress.java b/src/com/google/wireless/gdata2/contacts/data/EmailAddress.java new file mode 100644 index 0000000..bd5fed1 --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/data/EmailAddress.java @@ -0,0 +1,55 @@ +// Copyright 2007 The Android Open Source Project + +package com.google.wireless.gdata2.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; + private String displayName; + + /** + * default empty constructor + */ + public EmailAddress() {} + public EmailAddress(String address, String displayName, + byte type, String label, boolean isPrimary) { + super(type, label, isPrimary); + this.address = address; + this.displayName = displayName; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + /** + * Getter for displayName + */ + public String getDisplayName() { + return this.displayName; + } + + /** + * Setter for displayName + */ + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public void toString(StringBuffer sb) { + sb.append("EmailAddress"); + super.toString(sb); + if (address != null) sb.append(" address:").append(address); + if (displayName != null) sb.append(" displayName:").append(displayName); + } +} diff --git a/src/com/google/wireless/gdata2/contacts/data/Event.java b/src/com/google/wireless/gdata2/contacts/data/Event.java new file mode 100644 index 0000000..7bac378 --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/data/Event.java @@ -0,0 +1,55 @@ +/** + * Copyright (C) 2009 The Android Open Source Project + */ + +package com.google.wireless.gdata2.contacts.data; + +import java.util.Date; + +import com.google.wireless.gdata2.data.StringUtils; +import com.google.wireless.gdata2.parser.ParseException; + + +/** + * These elements describe events associated with a contact. + * They may be repeated. + */ +public class Event extends TypedElement { + public static final byte TYPE_ANNIVERSARY = 1; + public static final byte TYPE_OTHER = 2; + + private String startDate; + + /** + * default empty constructor + */ + public Event() {} + public Event(String startDate, byte type, String label) { + super(type, label); + this.startDate = startDate; + } + + /** + * StartDate associated with this event + */ + public String getStartDate() { + return this.startDate; + } + + /** + * StartDate associated with this event + */ + public void setStartDate(String startDate) { + this.startDate = startDate; + } + + public void toString(StringBuffer sb) { + sb.append("Event"); + super.toString(sb); + sb.append(" date:").append(startDate.toString()); + } +} + + + + diff --git a/src/com/google/wireless/gdata2/contacts/data/ExternalId.java b/src/com/google/wireless/gdata2/contacts/data/ExternalId.java new file mode 100644 index 0000000..f0c1bc2 --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/data/ExternalId.java @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2009 The Android Open Source Project + */ + +package com.google.wireless.gdata2.contacts.data; + +import com.google.wireless.gdata2.parser.ParseException; +import com.google.wireless.gdata2.data.StringUtils; + +/** + * Describes an ID of the contact in an external system of some + * kind. This element may be repeated.. + */ +public class ExternalId extends TypedElement { + public static final byte TYPE_ACCOUNT = 1; + public static final byte TYPE_CUSTOMER = 2; + public static final byte TYPE_NETWORK = 3; + public static final byte TYPE_ORGANIZATION = 4; + + + private String value; + + /** + * default empty constructor + */ + public ExternalId() {} + + /** + * constructor that allows initialization + */ + public ExternalId(String value, byte type, String label) { + super(type, label); + setValue(value); + } + + /** + * The value of this external ID. + */ + public String getValue() { + return this.value; + } + + /** + * The value of this external ID. + */ + public void setValue(String value) { + this.value = value; + } + + public void toString(StringBuffer sb) { + sb.append("ExternalId"); + super.toString(sb); + if (!StringUtils.isEmpty(value)) { + sb.append(" value:").append(value); + } + } + + /** + * override default behaviour, an externalId has its own rules for type and label + */ + public void validate() throws ParseException { + if (value == null) { + throw new ParseException("the value must be set"); + } + } +} + diff --git a/src/com/google/wireless/gdata2/contacts/data/GeoPt.java b/src/com/google/wireless/gdata2/contacts/data/GeoPt.java new file mode 100644 index 0000000..cb509d3 --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/data/GeoPt.java @@ -0,0 +1,76 @@ +// Copyright 2009 The Android Open Source Project + +package com.google.wireless.gdata2.contacts.data; + +/** + * The GeoPt GData type. + */ +public class GeoPt { + private String label; + private Float latitude; + private Float longitude; + private Float elevation; + + /** + * default empty constructor + */ + public GeoPt() {} + + // 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); + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + toString(sb); + return sb.toString(); + } +} diff --git a/src/com/google/wireless/gdata2/contacts/data/GroupEntry.java b/src/com/google/wireless/gdata2/contacts/data/GroupEntry.java new file mode 100644 index 0000000..09b0ccc --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/data/GroupEntry.java @@ -0,0 +1,40 @@ +// Copyright 2009 The Android Open Source Project + +package com.google.wireless.gdata2.contacts.data; + +import com.google.wireless.gdata2.data.Entry; +import com.google.wireless.gdata2.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; + } + + 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/gdata2/contacts/data/GroupMembershipInfo.java b/src/com/google/wireless/gdata2/contacts/data/GroupMembershipInfo.java new file mode 100644 index 0000000..d328773 --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/data/GroupMembershipInfo.java @@ -0,0 +1,53 @@ +// Copyright 2009 The Android Open Source Project + +package com.google.wireless.gdata2.contacts.data; + +import com.google.wireless.gdata2.data.StringUtils; +import com.google.wireless.gdata2.parser.ParseException; + +/** The groupMembershipInfo GData type. */ +public class GroupMembershipInfo { + private String group; + private boolean deleted; + + /** + * default empty constructor + */ + public GroupMembershipInfo() {} + + /** + * constructor that allows initializing the GroupMembershipInfo + */ + public GroupMembershipInfo(String groupId, boolean deleted) { + setGroup(groupId); + setDeleted(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/gdata2/contacts/data/GroupsFeed.java b/src/com/google/wireless/gdata2/contacts/data/GroupsFeed.java new file mode 100644 index 0000000..6f6511d --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/data/GroupsFeed.java @@ -0,0 +1,16 @@ +// Copyright 2009 The Android Open Source Project + +package com.google.wireless.gdata2.contacts.data; + +import com.google.wireless.gdata2.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/gdata2/contacts/data/ImAddress.java b/src/com/google/wireless/gdata2/contacts/data/ImAddress.java new file mode 100644 index 0000000..3f4c86c --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/data/ImAddress.java @@ -0,0 +1,72 @@ +// Copyright 2007 The Android Open Source Project + +package com.google.wireless.gdata2.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_NETMEETING = 10; + public static final byte PROTOCOL_NONE = 11; + + private byte protocolPredefined; + private String protocolCustom; + private String address; + + /** + * default empty constructor + */ + public ImAddress() {} + public ImAddress(String address, byte protocolPredefined, String protocolCustom, + byte type, String label, boolean isPrimary) { + super(type, label, isPrimary); + this.address = address; + this.protocolPredefined = protocolPredefined; + this.protocolCustom = protocolCustom; + } + + 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/gdata2/contacts/data/Jot.java b/src/com/google/wireless/gdata2/contacts/data/Jot.java new file mode 100644 index 0000000..81a92b9 --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/data/Jot.java @@ -0,0 +1,64 @@ +/** + * Copyright (C) 2009 The Android Open Source Project + */ +package com.google.wireless.gdata2.contacts.data; + +import com.google.wireless.gdata2.parser.ParseException; +import com.google.wireless.gdata2.data.StringUtils; + + +/** + * Storage for arbitrary pieces of information about the + * contact. Each jot has a type specified by the rel attribute + * and a text value. The element can be repeated. + */ +public class Jot extends TypedElement { + public static final byte TYPE_HOME = 1; + public static final byte TYPE_WORK = 2; + public static final byte TYPE_KEYWORDS = 3; + public static final byte TYPE_USER = 4; + public static final byte TYPE_OTHER = 5; + + private String value; + + /** + * default empty constructor + */ + public Jot() {} + + /** + * constructor that allows initialization + */ + public Jot(String value, byte type, String label) { + super(type, label); + setValue(value); + } + + /** + * override default behaviour, a jot is not relying on either + * label or type + */ + public void validate() throws ParseException {} + + /** + * The value of this Jot + */ + public String getValue() { + return this.value; + } + + /** + * The value of this Jot. + */ + public void setValue(String value) { + this.value = value; + } + + public void toString(StringBuffer sb) { + sb.append("Jot"); + super.toString(sb); + if (!StringUtils.isEmpty(value)) { + sb.append(" value:").append(value); + } + } +} diff --git a/src/com/google/wireless/gdata2/contacts/data/Language.java b/src/com/google/wireless/gdata2/contacts/data/Language.java new file mode 100644 index 0000000..0ca6ae3 --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/data/Language.java @@ -0,0 +1,103 @@ +/** + * Copyright (C) 2009 The Android Open Source Project + */ +package com.google.wireless.gdata2.contacts.data; + +import com.google.wireless.gdata2.parser.ParseException; +import com.google.wireless.gdata2.data.StringUtils; + + +/** + * Specifies the preferred languages of the contact. The element + * can be repeated. The language must be specified using one of + * two mutually exclusive methods: using the freeform label + * attribute, or using the code attribute, whose value must + * conform to the IETF BCP 47 specification. Describes an ID of + * the contact in an external system of some kind. This element + * may be repeated. + */ +public class Language { + + private String label; + private String code; + + /** + * default empty constructor + */ + public Language() {} + + /** + * constructor that allows initialization + */ + public Language(String label, String code) { + setLabel(label); + setCode(code); + } + + /** + * A freeform name of a language. Must not be empty or all + * whitespace. + */ + public String getLabel() { + return this.label; + } + + /** + * A freeform name of a language. Must not be empty or all + * whitespace. + */ + public void setLabel(String label) { + this.label = label; + } + + /** + * A language code conforming to the IETF BCP 47 specification. + * The server returns an error if a nonconformant value is + * provided. + */ + public String getCode() { + return this.code; + } + + /** + * A language code conforming to the IETF BCP 47 specification. + * The server returns an error if a nonconformant value is + * provided. + */ + public void setCode(String code) { + this.code = code; + } + + + + public String toString() { + StringBuffer sb = new StringBuffer(); + toString(sb); + return sb.toString(); + } + + + public void toString(StringBuffer sb) { + sb.append("Language"); + if (!StringUtils.isEmpty(code)) { + sb.append(" code:").append(code); + } + if (!StringUtils.isEmpty(label)) { + sb.append(" label:").append(label); + } + } + + /** + * A Language either has a code or a label, not both + */ + public void validate() throws ParseException { + if ((StringUtils.isEmpty(label) && + StringUtils.isEmpty(code)) || + (!StringUtils.isEmpty(label) && + !StringUtils.isEmpty(code))) { + throw new ParseException("exactly one of label or code must be set"); + } + } +} + + diff --git a/src/com/google/wireless/gdata2/contacts/data/Name.java b/src/com/google/wireless/gdata2/contacts/data/Name.java new file mode 100644 index 0000000..37ece2a --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/data/Name.java @@ -0,0 +1,167 @@ +// Copyright 2009 The Android Open Source Project + +package com.google.wireless.gdata2.contacts.data; + +/** + * Allows storing person's name in a structured way. Consists of + * given name, additional name, family name, prefix, suffix and + * full name. + */ +public class Name { + + private String fullName; + private String nameSuffix; + private String namePrefix; + private String familyName; + private String additionalName; + private String givenName; + private String givenNameYomi; + private String familyNameYomi; + private String additionalNameYomi; + + /** + * default empty constructor + */ + public Name() {} + + /** + * Getter for givenName, Person's given name. + */ + public String getGivenName() { + return this.givenName; + } + + /** + * Setter for givenName, Person's given name. + */ + public void setGivenName(String givenName) { + this.givenName = givenName; + } + + /** + * Getter for addtionalName, Additional name of the person, eg. + * middle name. + */ + public String getAdditionalName() { + return this.additionalName; + } + + /** + * Setter for addtionalName, Additional name of the person, eg. + * middle name. + */ + public void setAdditionalName(String addtionalName) { + this.additionalName = addtionalName; + } + + /** + * Getter for familyName, Person's family name. + */ + public String getFamilyName() { + return this.familyName; + } + + /** + * Setter for familyName, Person's family name. + */ + public void setFamilyName(String familyName) { + this.familyName = familyName; + } + + /** + * Getter for namePrefix, Honorific prefix, eg. 'Mr' or 'Mrs'. + */ + public String getNamePrefix() { + return this.namePrefix; + } + + /** + * Setter for namePrefix, Honorific prefix, eg. 'Mr' or 'Mrs'. + */ + public void setNamePrefix(String namePrefix) { + this.namePrefix = namePrefix; + } + + /** + * Getter for nameSuffix, Honorific suffix, eg. 'san' or 'III'. + */ + public String getNameSuffix() { + return this.nameSuffix; + } + + /** + * Setter for nameSuffix, Honorific suffix, eg. 'san' or 'III'. + */ + public void setNameSuffix(String nameSuffix) { + this.nameSuffix = nameSuffix; + } + + /** + * Getter for fullName, Unstructured representation of the name. + */ + public String getFullName() { + return this.fullName; + } + + /** + * Setter for fullName, Unstructured representation of the name. + */ + public void setFullName(String fullName) { + this.fullName = fullName; + } + + /** + * Getter for addtionalNameYomi, Phonetic representation + */ + public String getAdditionalNameYomi() { + return this.additionalNameYomi; + } + + /** + * Setter for addtionalNameYomi, Phonetic representation + */ + public void setAdditionalNameYomi(String addtionalNameYomi) { + this.additionalNameYomi = addtionalNameYomi; + } + + /** + * Getter for familyNameYomi, Phonetic representation + */ + public String getFamilyNameYomi() { + return this.familyNameYomi; + } + + /** + * Setter for familyNameYomi, Phonetic representation + */ + public void setFamilyNameYomi(String familyNameYomi) { + this.familyNameYomi = familyNameYomi; + } + + /** + * Getter for givenNameYomi, Phonetic representation + */ + public String getGivenNameYomi() { + return this.givenNameYomi; + } + + /** + * Setter for givenNameYomi, Phonetic representation + */ + public void setGivenNameYomi(String givenNameYomi) { + this.givenNameYomi = givenNameYomi; + } + + public void toString(StringBuffer sb) { + sb.append("Name"); + if (fullName != null) sb.append(" fullName:").append(fullName); + if (nameSuffix != null) sb.append(" nameSuffix:").append(nameSuffix); + if (namePrefix != null) sb.append(" namePrefix:").append(namePrefix); + if (familyName != null) sb.append(" familyName:").append(familyName); + if (additionalName != null) sb.append(" additionalName:").append(additionalName); + if (givenName != null) sb.append(" givenName:").append(givenName); + if (givenNameYomi != null) sb.append(" givenNameYomi:").append(givenNameYomi); + if (familyNameYomi != null) sb.append(" familyNameYomi:").append(familyNameYomi); + if (additionalNameYomi != null) sb.append(" additionalNameYomi:").append(additionalNameYomi); + } +} diff --git a/src/com/google/wireless/gdata2/contacts/data/Organization.java b/src/com/google/wireless/gdata2/contacts/data/Organization.java new file mode 100644 index 0000000..ca11252 --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/data/Organization.java @@ -0,0 +1,135 @@ +/** + * Copyright (C) 2009 The Android Open Source Project + */ + +package com.google.wireless.gdata2.contacts.data; + +/** 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 nameYomi; + private String title; + private String orgDepartment; + private String orgJobDescription; + private String orgSymbol; + private String where; + + /** + * default empty constructor + */ + public Organization() {} + public Organization(String name, String nameYomi, String title, String orgDepartment, + String orgJobDescription, String orgSymbol, String where, + byte type, String label, boolean isPrimary) { + super(type, label, isPrimary); + this.name = name; + this.nameYomi = nameYomi; + this.title = title; + this.orgDepartment = orgDepartment; + this.orgJobDescription = orgJobDescription; + this.orgSymbol = orgSymbol; + this.where = where; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * Getter for nameYomi + */ + public String getNameYomi() { + return this.nameYomi; + } + + /** + * Setter for nameYomi + */ + public void setNameYomi(String nameYomi) { + this.nameYomi = nameYomi; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + /** + * Getter for orgDepartment + */ + public String getOrgDepartment() { + return this.orgDepartment; + } + + /** + * Setter for orgDepartment + */ + public void setOrgDepartment(String orgDepartment) { + this.orgDepartment = orgDepartment; + } + + /** + * Getter for orgJobDescription + */ + public String getOrgJobDescription() { + return this.orgJobDescription; + } + + /** + * Setter for orgJobDescription + */ + public void setOrgJobDescription(String orgJobDescription) { + this.orgJobDescription = orgJobDescription; + } + + /** + * Getter for orgSymbol + */ + public String getOrgSymbol() { + return this.orgSymbol; + } + + /** + * Setter for orgSymbol + */ + public void setOrgSymbol(String orgSymbol) { + this.orgSymbol = orgSymbol; + } + + /** + * A place associated with the organization, e.g. office + * location. In Contacts, this is just a string value. + */ + public String getWhere() { + return this.where; + } + + /** + * A place associated with the organization, e.g. office + * location. In Contacts, this is just a string value. + */ + public void setWhere(String where) { + this.where = where; + } + + 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); + if (orgDepartment != null) sb.append(" orgDepartment:").append(orgDepartment); + if (orgJobDescription != null) sb.append(" orgJobDescription:").append(orgJobDescription); + if (orgSymbol != null) sb.append(" orgSymbol:").append(orgSymbol); + if (nameYomi != null) sb.append(" nameYomi:").append(nameYomi); + } +} diff --git a/src/com/google/wireless/gdata2/contacts/data/PhoneNumber.java b/src/com/google/wireless/gdata2/contacts/data/PhoneNumber.java new file mode 100644 index 0000000..a4b4c62 --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/data/PhoneNumber.java @@ -0,0 +1,55 @@ +// Copyright 2009 The Android Open Source Project + +package com.google.wireless.gdata2.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_ASSISTANT = 7; + public static final byte TYPE_CALLBACK = 8; + public static final byte TYPE_CAR = 9; + public static final byte TYPE_COMPANY_MAIN = 10; + public static final byte TYPE_ISDN = 11; + public static final byte TYPE_MAIN = 12; + public static final byte TYPE_OTHER_FAX = 13; + public static final byte TYPE_RADIO = 14; + public static final byte TYPE_TELEX = 15; + public static final byte TYPE_TTY_TDD = 16; + public static final byte TYPE_WORK_MOBILE = 17; + public static final byte TYPE_WORK_PAGER = 18; + public static final byte TYPE_OTHER = 19; + + private String phoneNumber; + + /** + * default empty constructor + */ + public PhoneNumber() {} + public PhoneNumber(String phoneNumber, byte type, String label, boolean isPrimary) { + super(type, label, isPrimary); + this.phoneNumber = 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/gdata2/contacts/data/Relation.java b/src/com/google/wireless/gdata2/contacts/data/Relation.java new file mode 100644 index 0000000..1944c49 --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/data/Relation.java @@ -0,0 +1,69 @@ +/** + * Copyright (C) 2007 The Android Open Source Project + */ + +package com.google.wireless.gdata2.contacts.data; + +import com.google.wireless.gdata2.parser.ParseException; +import com.google.wireless.gdata2.data.StringUtils; + + +/** + * This element describe another entity (usually a person) + * that is in a relation of some kind with the contact + */ +public class Relation extends TypedElement { + public static final byte TYPE_ASSISTANT = 1; + public static final byte TYPE_BROTHER = 2; + public static final byte TYPE_CHILD = 3; + public static final byte TYPE_DOMESTICPARTNER = 4; + public static final byte TYPE_FATHER = 5; + public static final byte TYPE_FRIEND = 6; + public static final byte TYPE_MANAGER = 7; + public static final byte TYPE_MOTHER = 8; + public static final byte TYPE_PARENT = 9; + public static final byte TYPE_PARTNER = 10; + public static final byte TYPE_REFERREDBY = 11; + public static final byte TYPE_RELATIVE = 12; + public static final byte TYPE_SISTER = 13; + public static final byte TYPE_SPOUSE = 14; + + private String text; + + /** + * default empty constructor + */ + public Relation() {} + public Relation(String text, byte type, String label) { + super(type, label); + this.text = text; + } + + + + /** + * The entity in relation with the contact. + */ + public String getText() { + return this.text; + } + + /** + * The entity in relation with the contact. + */ + public void setText(String text) { + this.text = text; + } + + + public void toString(StringBuffer sb) { + sb.append("Relation"); + super.toString(sb); + if (!StringUtils.isEmpty(text)) { + sb.append(" text:").append(text); + } + } +} + + + diff --git a/src/com/google/wireless/gdata2/contacts/data/StructuredPostalAddress.java b/src/com/google/wireless/gdata2/contacts/data/StructuredPostalAddress.java new file mode 100644 index 0000000..79399f5 --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/data/StructuredPostalAddress.java @@ -0,0 +1,206 @@ +// Copyright 2009 The Android Open Source Project + +package com.google.wireless.gdata2.contacts.data; + +/** + * Postal address split into components. It allows to store the + * address in locale independent format. The fields can be + * interpreted and used to generate formatted, locale dependent + * address. + * Not all the properties of gd:structuredPostalAddress are supported + * by the Contacts API. The unsupported properties are the + * gd:agent, gd:housename, and gd:subregion subelements, and the + * attributes mailClass and usage. + */ +public class StructuredPostalAddress 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 street; + private String pobox; + private String neighborhood; + private String city; + private String region; + private String postcode; + private String country; + private String formattedAddress; + + /** + * default empty constructor + */ + public StructuredPostalAddress() {} + public StructuredPostalAddress(String street, String pobox, String city, String postcode, + String country, String region, String neighborhood, String formattedAddress, + byte type, String label, boolean isPrimary) { + super(type, label, isPrimary); + this.street = street; + this.pobox = pobox; + this.city = city; + this.postcode = postcode; + this.country = country; + this.region = region; + this.neighborhood = neighborhood; + this.formattedAddress = formattedAddress; + } + + /** + * Getter for street + * Can be street, avenue, road, etc. This element also includes + * the house number and room/apartment/flat/floor number + */ + public String getStreet() { + return this.street; + } + + /** + * Setter for street + * Can be street, avenue, road, etc. This element also includes + * the house number and room/apartment/flat/floor number + */ + public void setStreet(String street) { + this.street = street; + } + + /** + * Getter for pobox + * Covers actual P.O. boxes, drawers, locked bags, etc. This is + * usually but not always mutually exclusive with street. + */ + public String getPobox() { + return this.pobox; + } + + /** + * Setter for pobox + * Covers actual P.O. boxes, drawers, locked bags, etc. This is + * usually but not always mutually exclusive with street. + */ + public void setPobox(String pobox) { + this.pobox = pobox; + } + + /** + * Getter for neighborhood + * This is used to disambiguate a street address when a city + * contains more than one street with the same name, or to + * specify a small place whose mail is routed through a larger + * postal town. In China it could be a county or a minor city. + */ + public String getNeighborhood() { + return this.neighborhood; + } + + /** + * Setter for neighborhood + * This is used to disambiguate a street address when a city + * contains more than one street with the same name, or to + * specify a small place whose mail is routed through a larger + * postal town. In China it could be a county or a minor city. + */ + public void setNeighborhood(String neighborhood) { + this.neighborhood = neighborhood; + } + + /** + * Getter for city + * Can be city, village, town, borough, etc. This is the postal + * town and not necessarily the place of residence or place of + * business. + */ + public String getCity() { + return this.city; + } + + /** + * Setter for city + * Can be city, village, town, borough, etc. This is the postal + * town and not necessarily the place of residence or place of + * business. + */ + public void setCity(String city) { + this.city = city; + } + + + /** + * Getter for region + * A state, province, county (in Ireland), Land (in Germany), + * departement (in France), etc. + */ + public String getRegion() { + return this.region; + } + + /** + * Setter for region + * A state, province, county (in Ireland), Land (in Germany), + * departement (in France), etc. + */ + public void setRegion(String region) { + this.region = region; + } + + /** + * Getter for postcode + * Postal code. Usually country-wide, but sometimes specific to + * the city (e.g. "2" in "Dublin 2, Ireland" addresses). + */ + public String getPostcode() { + return this.postcode; + } + + /** + * Setter for postcode + * Postal code. Usually country-wide, but sometimes specific to + * the city (e.g. "2" in "Dublin 2, Ireland" addresses). + */ + public void setPostcode(String postcode) { + this.postcode = postcode; + } + + /** + * Getter for country + * The name or code of the country. + */ + public String getCountry() { + return this.country; + } + + /** + * Setter for country + * The name or code of the country. + */ + public void setCountry(String country) { + this.country = country; + } + + /** + * Getter for formatedAddress + * The full, unstructured postal address. + */ + public String getFormattedAddress() { + return this.formattedAddress; + } + + /** + * Setter for formatedAddress + * The full, unstructured postal address. + */ + public void setFormattedAddress(String formattedAddress) { + this.formattedAddress = formattedAddress; + } + + public void toString(StringBuffer sb) { + sb.append("PostalAddress"); + super.toString(sb); + if (street != null) sb.append(" street:").append(street); + if (pobox != null) sb.append(" pobox:").append(pobox); + if (neighborhood != null) sb.append(" neighborhood:").append(neighborhood); + if (city != null) sb.append(" city:").append(city); + if (region != null) sb.append(" region:").append(region); + if (postcode != null) sb.append(" postcode:").append(postcode); + if (country != null) sb.append(" country:").append(country); + if (formattedAddress != null) sb.append(" formattedAddress:").append(formattedAddress); + } +} diff --git a/src/com/google/wireless/gdata2/contacts/data/TypedElement.java b/src/com/google/wireless/gdata2/contacts/data/TypedElement.java new file mode 100644 index 0000000..20595b7 --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/data/TypedElement.java @@ -0,0 +1,56 @@ +/** + * Copyright (C) 2009 The Android Open Source Project + */ +package com.google.wireless.gdata2.contacts.data; + +import com.google.wireless.gdata2.parser.ParseException; + + +/** + * Contains attributes that are common to all elements in a ContactEntry. + */ +public abstract class TypedElement { + public static final byte TYPE_NONE = -1; + private byte type = TYPE_NONE; + + private String label; + + public TypedElement() {} + public TypedElement(byte type, String label) { + this.type = type; + this.label = label; + } + + 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); + 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/gdata2/contacts/data/UserDefinedField.java b/src/com/google/wireless/gdata2/contacts/data/UserDefinedField.java new file mode 100644 index 0000000..c127a68 --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/data/UserDefinedField.java @@ -0,0 +1,95 @@ +/** + * Copyright (C) 2007 The Android Open Source Project + */ + +package com.google.wireless.gdata2.contacts.data; + +import com.google.wireless.gdata2.parser.ParseException; +import com.google.wireless.gdata2.data.StringUtils; + + +/** + * Represents an arbitrary key-value pair attached to the contact. + */ +public class UserDefinedField { + + private String key; + private String value; + + + /** + * default empty constructor + */ + public UserDefinedField() {} + + /** + * Default constructor + */ + public UserDefinedField(String key, String value) + { + this.key = key; + this.value = value; + } + + + /** + * A simple string value used to name this field. + * Case-sensitive + */ + public String getKey() { + return this.key; + } + + /** + * A simple string value used to name this field. Case-sensitive + */ + public void setKey(String key) { + this.key = key; + } + + /** + * The value of this field. + */ + public String getValue() { + return this.value; + } + + /** + * The value of this field. + */ + public void setValue(String value) { + this.value = value; + } + + + + public String toString() { + StringBuffer sb = new StringBuffer(); + toString(sb); + return sb.toString(); + } + + + public void toString(StringBuffer sb) { + sb.append("UserDefinedField"); + if (!StringUtils.isEmpty(key)) { + sb.append(" key:").append(key); + } + if (!StringUtils.isEmpty(value)) { + sb.append(" value:").append(value); + } + } + + /** + * Currently empty, will be filled when the parser is done + * + */ + public void validate() throws ParseException { + if (StringUtils.isEmpty(key)) { + throw new ParseException("key has to be set"); + } + } +} + + + diff --git a/src/com/google/wireless/gdata2/contacts/data/WebSite.java b/src/com/google/wireless/gdata2/contacts/data/WebSite.java new file mode 100644 index 0000000..6c3e59b --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/data/WebSite.java @@ -0,0 +1,55 @@ +// Copyright 2007 The Android Open Source Project + +package com.google.wireless.gdata2.contacts.data; + +import com.google.wireless.gdata2.data.StringUtils; + +/** + * Describes websites associated with the contact, including links. + * May be repeated. + */ +public class WebSite extends ContactsElement { + /** The phone number type. */ + public static final byte TYPE_HOMEPAGE = 1; + public static final byte TYPE_BLOG = 2; + public static final byte TYPE_PROFILE = 3; + public static final byte TYPE_HOME = 4; + public static final byte TYPE_WORK = 5; + public static final byte TYPE_OTHER = 6; + public static final byte TYPE_FTP = 7; + + private String href; + + /** + * default empty constructor + */ + public WebSite() {} + public WebSite(String href, byte type, String label, boolean isPrimary) { + super(type, label, isPrimary); + this.href = href; + } + + /** + * The URL of the website. + */ + public String getHRef() { + return this.href; + } + + /** + * The URL of the website. + */ + public void setHRef(String href) { + this.href = href; + } + + + public void toString(StringBuffer sb) { + sb.append("WebSite"); + super.toString(sb); + if (!StringUtils.isEmpty(href)) { + sb.append(" href:").append(href); + } + } +} + diff --git a/src/com/google/wireless/gdata/spreadsheets/package.html b/src/com/google/wireless/gdata2/contacts/data/package.html index 1c9bf9d..1c9bf9d 100644 --- a/src/com/google/wireless/gdata/spreadsheets/package.html +++ b/src/com/google/wireless/gdata2/contacts/data/package.html diff --git a/src/com/google/wireless/gdata/spreadsheets/parser/package.html b/src/com/google/wireless/gdata2/contacts/package.html index 1c9bf9d..1c9bf9d 100644 --- a/src/com/google/wireless/gdata/spreadsheets/parser/package.html +++ b/src/com/google/wireless/gdata2/contacts/package.html diff --git a/src/com/google/wireless/gdata/spreadsheets/parser/xml/package.html b/src/com/google/wireless/gdata2/contacts/parser/package.html index 1c9bf9d..1c9bf9d 100644 --- a/src/com/google/wireless/gdata/spreadsheets/parser/xml/package.html +++ b/src/com/google/wireless/gdata2/contacts/parser/package.html diff --git a/src/com/google/wireless/gdata2/contacts/parser/xml/XmlContactsGDataParser.java b/src/com/google/wireless/gdata2/contacts/parser/xml/XmlContactsGDataParser.java new file mode 100644 index 0000000..47ef50a --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/parser/xml/XmlContactsGDataParser.java @@ -0,0 +1,639 @@ +// Copyright 2009 The Android Open Source Project + +package com.google.wireless.gdata2.contacts.parser.xml; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Hashtable; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import com.google.wireless.gdata2.contacts.data.*; +import com.google.wireless.gdata2.data.Entry; +import com.google.wireless.gdata2.data.ExtendedProperty; +import com.google.wireless.gdata2.data.Feed; +import com.google.wireless.gdata2.data.XmlUtils; +import com.google.wireless.gdata2.parser.ParseException; +import com.google.wireless.gdata2.parser.xml.XmlGDataParser; + +/** + * 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"; + + /** 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_ASSISTANT = GD_NAMESPACE + "assistant"; + public static final String TYPESTRING_CALLBACK = GD_NAMESPACE + "callback"; + public static final String TYPESTRING_CAR = GD_NAMESPACE + "car"; + public static final String TYPESTRING_COMPANY_MAIN = GD_NAMESPACE + "company_main"; + public static final String TYPESTRING_ISDN = GD_NAMESPACE + "isdn"; + public static final String TYPESTRING_MAIN = GD_NAMESPACE + "main"; + public static final String TYPESTRING_OTHER_FAX = GD_NAMESPACE + "other_fax"; + public static final String TYPESTRING_RADIO = GD_NAMESPACE + "radio"; + public static final String TYPESTRING_TELEX = GD_NAMESPACE + "telex"; + public static final String TYPESTRING_TTY_TDD = GD_NAMESPACE + "tty_tdd"; + public static final String TYPESTRING_WORK_MOBILE = GD_NAMESPACE + "work_mobile"; + public static final String TYPESTRING_WORK_PAGER = GD_NAMESPACE + "work_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"; + public static final String IM_PROTOCOL_NETMEETING = GD_NAMESPACE + "netmeeting"; + + public static final String TYPESTRING_CALENDARLINK_HOME = "home"; + public static final String TYPESTRING_CALENDARLINK_WORK = "work"; + public static final String TYPESTRING_CALENDARLINK_FREEBUSY = "free-busy"; + + public static final String TYPESTRING_EVENT_ANNIVERARY = "anniversary"; + public static final String TYPESTRING_EVENT_OTHER = "other"; + + public static final String TYPESTRING_EXTERNALID_ACCOUNT = "account"; + public static final String TYPESTRING_EXTERNALID_CUSTOMER = "customer"; + public static final String TYPESTRING_EXTERNALID_NETWORK = "network"; + public static final String TYPESTRING_EXTERNALID_ORGANIZATION = "organization"; + + public static final String TYPESTRING_JOT_HOME = TYPESTRING_CALENDARLINK_HOME; + public static final String TYPESTRING_JOT_WORK = TYPESTRING_CALENDARLINK_WORK; + public static final String TYPESTRING_JOT_OTHER = TYPESTRING_EVENT_OTHER; + public static final String TYPESTRING_JOT_KEYWORDS = "keywords"; + public static final String TYPESTRING_JOT_USER = "user"; + + public static final String TYPESTRING_PRIORITY_HIGH = "high"; + public static final String TYPESTRING_PRIORITY_LOW = "low"; + public static final String TYPESTRING_PRIORITY_NORMAL = "normal"; + + public static final String TYPESTRING_RELATION_ASSISTANT = "assistant"; + public static final String TYPESTRING_RELATION_BROTHER = "brother"; + public static final String TYPESTRING_RELATION_CHILD = "child"; + public static final String TYPESTRING_RELATION_DOMESTICPARTNER = "domestic-partner"; + public static final String TYPESTRING_RELATION_FATHER = "father"; + public static final String TYPESTRING_RELATION_FRIEND = "friend"; + public static final String TYPESTRING_RELATION_MANAGER = "manager"; + public static final String TYPESTRING_RELATION_MOTHER = "mother"; + public static final String TYPESTRING_RELATION_PARENT = "parent"; + public static final String TYPESTRING_RELATION_PARTNER = "partner"; + public static final String TYPESTRING_RELATION_REFERREDBY = "referred-by"; + public static final String TYPESTRING_RELATION_RELATIVE = "relative"; + public static final String TYPESTRING_RELATION_SISTER = "sister"; + public static final String TYPESTRING_RELATION_SPOUSE = "spouse"; + + public static final String TYPESTRING_SENSITIVITY_CONFIDENTIAL = "confidential"; + public static final String TYPESTRING_SENSITIVITY_NORMAL = "normal"; + public static final String TYPESTRING_SENSITIVITY_PERSONAL = "personal"; + public static final String TYPESTRING_SENSITIVITY_PRIVATE = "private"; + + public static final String TYPESTRING_WEBSITE_HOMEPAGE = "home-page"; + public static final String TYPESTRING_WEBSITE_BLOG = "blog"; + public static final String TYPESTRING_WEBSITE_PROFILE = "profile"; + public static final String TYPESTRING_WEBSITE_HOME = TYPESTRING_CALENDARLINK_HOME; + public static final String TYPESTRING_WEBSITE_WORK = TYPESTRING_CALENDARLINK_WORK; + public static final String TYPESTRING_WEBSITE_OTHER = TYPESTRING_EVENT_OTHER; + public static final String TYPESTRING_WEBSITE_FTP = "ftp"; + + 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; + private static final Hashtable REL_TO_TYPE_CALENDARLINK; + private static final Hashtable REL_TO_TYPE_EVENT; + private static final Hashtable REL_TO_TYPE_EXTERNALID; + private static final Hashtable REL_TO_TYPE_JOT; + private static final Hashtable REL_TO_TYPE_PRIORITY; + private static final Hashtable REL_TO_TYPE_RELATION; + private static final Hashtable REL_TO_TYPE_SENSITIVITY; + private static final Hashtable REL_TO_TYPE_WEBSITE; + + 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; + public static final Hashtable TYPE_TO_REL_CALENDARLINK; + public static final Hashtable TYPE_TO_REL_EVENT; + public static final Hashtable TYPE_TO_REL_EXTERNALID; + public static final Hashtable TYPE_TO_REL_JOT; + public static final Hashtable TYPE_TO_REL_PRIORITY; + public static final Hashtable TYPE_TO_REL_RELATION; + public static final Hashtable TYPE_TO_REL_SENSITIVITY; + public static final Hashtable TYPE_TO_REL_WEBSITE; + + 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)); + 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_ASSISTANT, new Byte(PhoneNumber.TYPE_ASSISTANT)); + map.put(TYPESTRING_CALLBACK, new Byte(PhoneNumber.TYPE_CALLBACK)); + map.put(TYPESTRING_CAR, new Byte(PhoneNumber.TYPE_CAR)); + map.put(TYPESTRING_COMPANY_MAIN, new Byte(PhoneNumber.TYPE_COMPANY_MAIN)); + map.put(TYPESTRING_ISDN, new Byte(PhoneNumber.TYPE_ISDN)); + map.put(TYPESTRING_MAIN, new Byte(PhoneNumber.TYPE_MAIN)); + map.put(TYPESTRING_OTHER_FAX, new Byte(PhoneNumber.TYPE_OTHER_FAX)); + map.put(TYPESTRING_RADIO, new Byte(PhoneNumber.TYPE_RADIO)); + map.put(TYPESTRING_TELEX, new Byte(PhoneNumber.TYPE_TELEX)); + map.put(TYPESTRING_TTY_TDD, new Byte(PhoneNumber.TYPE_TTY_TDD)); + map.put(TYPESTRING_WORK_MOBILE, new Byte(PhoneNumber.TYPE_WORK_MOBILE)); + map.put(TYPESTRING_WORK_PAGER, new Byte(PhoneNumber.TYPE_WORK_PAGER)); + 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(StructuredPostalAddress.TYPE_HOME)); + map.put(TYPESTRING_WORK, new Byte(StructuredPostalAddress.TYPE_WORK)); + map.put(TYPESTRING_OTHER, new Byte(StructuredPostalAddress.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)); + map.put(IM_PROTOCOL_NETMEETING, new Byte(ImAddress.PROTOCOL_NETMEETING)); + + IM_PROTOCOL_STRING_TO_TYPE_MAP = map; + IM_PROTOCOL_TYPE_TO_STRING_MAP = swapMap(map); + + map = new Hashtable(); + map.put(TYPESTRING_CALENDARLINK_HOME, new Byte(CalendarLink.TYPE_HOME)); + map.put(TYPESTRING_CALENDARLINK_WORK, new Byte(CalendarLink.TYPE_WORK)); + map.put(TYPESTRING_CALENDARLINK_FREEBUSY, new Byte(CalendarLink.TYPE_FREE_BUSY)); + REL_TO_TYPE_CALENDARLINK = map; + TYPE_TO_REL_CALENDARLINK = swapMap(map); + + map = new Hashtable(); + map.put(TYPESTRING_EVENT_ANNIVERARY, new Byte(Event.TYPE_ANNIVERSARY)); + map.put(TYPESTRING_EVENT_OTHER, new Byte(Event.TYPE_OTHER)); + REL_TO_TYPE_EVENT = map; + TYPE_TO_REL_EVENT = swapMap(map); + + map = new Hashtable(); + map.put(TYPESTRING_EXTERNALID_ACCOUNT, new Byte(ExternalId.TYPE_ACCOUNT)); + map.put(TYPESTRING_EXTERNALID_CUSTOMER, new Byte(ExternalId.TYPE_CUSTOMER)); + map.put(TYPESTRING_EXTERNALID_NETWORK, new Byte(ExternalId.TYPE_NETWORK)); + map.put(TYPESTRING_EXTERNALID_ORGANIZATION, new Byte(ExternalId.TYPE_ORGANIZATION)); + REL_TO_TYPE_EXTERNALID = map; + TYPE_TO_REL_EXTERNALID = swapMap(map); + + map = new Hashtable(); + map.put(TYPESTRING_JOT_HOME, new Byte(Jot.TYPE_HOME)); + map.put(TYPESTRING_JOT_KEYWORDS, new Byte(Jot.TYPE_KEYWORDS)); + map.put(TYPESTRING_JOT_OTHER, new Byte(Jot.TYPE_OTHER)); + map.put(TYPESTRING_JOT_USER, new Byte(Jot.TYPE_USER)); + map.put(TYPESTRING_JOT_WORK, new Byte(Jot.TYPE_WORK)); + REL_TO_TYPE_JOT = map; + TYPE_TO_REL_JOT = swapMap(map); + + map = new Hashtable(); + map.put(TYPESTRING_PRIORITY_HIGH, new Byte(ContactEntry.TYPE_PRIORITY_HIGH)); + map.put(TYPESTRING_PRIORITY_NORMAL, new Byte(ContactEntry.TYPE_PRIORITY_NORMAL)); + map.put(TYPESTRING_PRIORITY_LOW, new Byte(ContactEntry.TYPE_PRIORITY_LOW)); + REL_TO_TYPE_PRIORITY = map; + TYPE_TO_REL_PRIORITY = swapMap(map); + + map = new Hashtable(); + map.put(TYPESTRING_RELATION_ASSISTANT, new Byte(Relation.TYPE_ASSISTANT)); + map.put(TYPESTRING_RELATION_BROTHER, new Byte(Relation.TYPE_BROTHER)); + map.put(TYPESTRING_RELATION_CHILD, new Byte(Relation.TYPE_CHILD)); + map.put(TYPESTRING_RELATION_DOMESTICPARTNER, new Byte(Relation.TYPE_DOMESTICPARTNER)); + map.put(TYPESTRING_RELATION_FATHER, new Byte(Relation.TYPE_FATHER)); + map.put(TYPESTRING_RELATION_FRIEND, new Byte(Relation.TYPE_FRIEND)); + map.put(TYPESTRING_RELATION_MANAGER, new Byte(Relation.TYPE_MANAGER)); + map.put(TYPESTRING_RELATION_MOTHER, new Byte(Relation.TYPE_MOTHER)); + map.put(TYPESTRING_RELATION_PARENT, new Byte(Relation.TYPE_PARENT)); + map.put(TYPESTRING_RELATION_PARTNER, new Byte(Relation.TYPE_PARTNER)); + map.put(TYPESTRING_RELATION_REFERREDBY, new Byte(Relation.TYPE_REFERREDBY)); + map.put(TYPESTRING_RELATION_RELATIVE, new Byte(Relation.TYPE_RELATIVE)); + map.put(TYPESTRING_RELATION_SISTER, new Byte(Relation.TYPE_SISTER)); + map.put(TYPESTRING_RELATION_SPOUSE, new Byte(Relation.TYPE_SPOUSE)); + REL_TO_TYPE_RELATION = map; + TYPE_TO_REL_RELATION = swapMap(map); + + map = new Hashtable(); + map.put(TYPESTRING_SENSITIVITY_CONFIDENTIAL, + new Byte(ContactEntry.TYPE_SENSITIVITY_CONFIDENTIAL)); + map.put(TYPESTRING_SENSITIVITY_NORMAL, + new Byte(ContactEntry.TYPE_SENSITIVITY_NORMAL)); + map.put(TYPESTRING_SENSITIVITY_PERSONAL, + new Byte(ContactEntry.TYPE_SENSITIVITY_PERSONAL)); + map.put(TYPESTRING_SENSITIVITY_PRIVATE, + new Byte(ContactEntry.TYPE_SENSITIVITY_PRIVATE)); + REL_TO_TYPE_SENSITIVITY= map; + TYPE_TO_REL_SENSITIVITY = swapMap(map); + + map = new Hashtable(); + map.put(TYPESTRING_WEBSITE_BLOG, new Byte(WebSite.TYPE_BLOG)); + map.put(TYPESTRING_WEBSITE_HOMEPAGE, new Byte(WebSite.TYPE_HOMEPAGE)); + map.put(TYPESTRING_WEBSITE_PROFILE, new Byte(WebSite.TYPE_PROFILE)); + map.put(TYPESTRING_WEBSITE_HOME, new Byte(WebSite.TYPE_HOME)); + map.put(TYPESTRING_WEBSITE_WORK, new Byte(WebSite.TYPE_WORK)); + map.put(TYPESTRING_WEBSITE_OTHER, new Byte(WebSite.TYPE_OTHER)); + map.put(TYPESTRING_WEBSITE_FTP, new Byte(WebSite.TYPE_FTP)); + + REL_TO_TYPE_WEBSITE = map; + TYPE_TO_REL_WEBSITE = 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.gdata2.parser.xml.XmlGDataParser#createFeed() + */ + protected Feed createFeed() { + return new ContactsFeed(); + } + + /* + * (non-Javadoc) + * @see com.google.wireless.gdata2.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(); + String ns = parser.getNamespace(); + + if (XmlGDataParser.NAMESPACE_GD_URI.equals(ns)) { + if (XmlNametable.GD_EMAIL.equals(name)) { + EmailAddress emailAddress = new EmailAddress(); + parseContactsElement(emailAddress, parser, REL_TO_TYPE_EMAIL); + emailAddress.setDisplayName(parser.getAttributeValue(null /* ns */, + XmlNametable.GD_EMAIL_DISPLAYNAME)); + emailAddress.setAddress(parser.getAttributeValue(null /* ns */, + XmlNametable.GD_ADDRESS)); + contactEntry.addEmailAddress(emailAddress); + } else if (XmlNametable.GD_DELETED.equals(name)) { + contactEntry.setDeleted(true); + } else if (XmlNametable.GD_IM.equals(name)) { + ImAddress imAddress = new ImAddress(); + parseContactsElement(imAddress, parser, REL_TO_TYPE_IM); + imAddress.setAddress(parser.getAttributeValue(null /* ns */, + XmlNametable.GD_ADDRESS)); + imAddress.setLabel(parser.getAttributeValue(null /* ns */, + XmlNametable.LABEL)); + String protocolString = parser.getAttributeValue(null /* ns */, + XmlNametable.GD_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 (XmlNametable.GD_SPA.equals(name)) { + StructuredPostalAddress postalAddress = new StructuredPostalAddress(); + parseContactsElement(postalAddress, parser, REL_TO_TYPE_POSTAL); + handleStructuredPostalAddressSubElement(postalAddress, parser); + contactEntry.addPostalAddress(postalAddress); + } else if (XmlNametable.GD_PHONENUMBER.equals(name)) { + PhoneNumber phoneNumber = new PhoneNumber(); + parseContactsElement(phoneNumber, parser, REL_TO_TYPE_PHONE); + phoneNumber.setPhoneNumber(XmlUtils.extractChildText(parser)); + contactEntry.addPhoneNumber(phoneNumber); + } else if (XmlNametable.GD_ORGANIZATION.equals(name)) { + Organization organization = new Organization(); + parseContactsElement(organization, parser, REL_TO_TYPE_ORGANIZATION); + handleOrganizationSubElement(organization, parser); + contactEntry.addOrganization(organization); + } else if (XmlNametable.GD_EXTENDEDPROPERTY.equals(name)) { + ExtendedProperty extendedProperty = new ExtendedProperty(); + parseExtendedProperty(extendedProperty); + contactEntry.addExtendedProperty(extendedProperty); + } else if (XmlNametable.GD_NAME.equals(name)) { + Name n = new Name(); + handleNameSubElement(n, parser); + contactEntry.setName(n); + } + } else if (XmlContactsGDataParser.NAMESPACE_CONTACTS_URI.equals(ns)) { + if (XmlNametable.GC_GMI.equals(name)) { + GroupMembershipInfo group = new GroupMembershipInfo(); + group.setGroup(parser.getAttributeValue(null /* ns */, + XmlNametable.HREF)); + group.setDeleted("true".equals(parser.getAttributeValue(null /* ns */, + XmlNametable.GD_DELETED))); + contactEntry.addGroup(group); + } else if (XmlNametable.GC_BIRTHDAY.equals(name)) { + contactEntry.setBirthday(parser.getAttributeValue(null /* ns */, + XmlNametable.GD_WHEN)); + } else if (XmlNametable.GC_BILLINGINFO.equals(name)) { + contactEntry.setBillingInformation(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GC_CALENDARLINK.equals(name)) { + CalendarLink cl = new CalendarLink(); + parseContactsElement(cl, parser, REL_TO_TYPE_CALENDARLINK); + cl.setHRef(parser.getAttributeValue(null /* ns */, XmlNametable.HREF)); + contactEntry.addCalendarLink(cl); + } else if (XmlNametable.GC_DIRECTORYSERVER.equals(name)) { + contactEntry.setDirectoryServer(XmlUtils.extractChildText(parser)); + } else if ("event".equals(name)) { + Event event = new Event(); + parseTypedElement(event, parser, REL_TO_TYPE_EVENT); + handleEventSubElement(event, parser); + contactEntry.addEvent(event); + } else if (XmlNametable.GC_EXTERNALID.equals(name)) { + ExternalId externalId = new ExternalId(); + parseTypedElement(externalId, parser, REL_TO_TYPE_EXTERNALID); + externalId.setValue(parser.getAttributeValue(null /* ns */, XmlNametable.VALUE)); + contactEntry.addExternalId(externalId); + } else if (XmlNametable.GC_GENDER.equals(name)) { + contactEntry.setGender(parser.getAttributeValue(null /* ns */, XmlNametable.VALUE)); + } else if (XmlNametable.GC_HOBBY.equals(name)) { + contactEntry.addHobby(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GC_INITIALS.equals(name)) { + contactEntry.setInitials(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GC_JOT.equals(name)) { + Jot jot = new Jot(); + parseTypedElement(jot, parser, REL_TO_TYPE_JOT); + jot.setLabel(XmlUtils.extractChildText(parser)); + contactEntry.addJot(jot); + } else if (XmlNametable.GC_LANGUAGE.equals(name)) { + Language language = new Language(); + language.setCode(parser.getAttributeValue(null /* ns */, XmlNametable.CODE)); + language.setLabel(parser.getAttributeValue(null /* */, XmlNametable.LABEL)); + contactEntry.addLanguage(language); + } else if (XmlNametable.GC_MAIDENNAME.equals(name)) { + contactEntry.setMaidenName(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GC_MILEAGE.equals(name)) { + contactEntry.setMileage(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GC_NICKNAME.equals(name)) { + contactEntry.setNickname(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GC_OCCUPATION.equals(name)) { + contactEntry.setOccupation(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GC_PRIORITY.equals(name)) { + contactEntry.setPriority(relToType( + parser.getAttributeValue(null /* ns */, XmlNametable.REL), + REL_TO_TYPE_PRIORITY)); + } else if (XmlNametable.GC_RELATION.equals(name)) { + Relation relation = new Relation(); + parseTypedElement(relation, parser, REL_TO_TYPE_RELATION); + relation.setText(XmlUtils.extractChildText(parser)); + contactEntry.addRelation(relation); + } else if (XmlNametable.GC_SENSITIVITY.equals(name)) { + contactEntry.setSensitivity(relToType( + parser.getAttributeValue(null /* ns */, XmlNametable.REL), + REL_TO_TYPE_SENSITIVITY)); + } else if (XmlNametable.GC_SHORTNAME.equals(name)) { + contactEntry.setShortName(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GC_SUBJECT.equals(name)) { + contactEntry.setSubject(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GC_UDF.equals(name)) { + UserDefinedField udf = new UserDefinedField(); + udf.setKey(parser.getAttributeValue(null /* ns */, XmlNametable.KEY)); + udf.setValue(parser.getAttributeValue(null /* ns */, XmlNametable.VALUE)); + contactEntry.addUserDefinedField(udf); + } else if (XmlNametable.GC_WEBSITE.equals(name)) { + WebSite ws = new WebSite(); + parseContactsElement(ws, parser, REL_TO_TYPE_WEBSITE); + ws.setHRef(parser.getAttributeValue(null /* ns */, XmlNametable.HREF)); + contactEntry.addWebSite(ws); + } + } + } + + protected void handleExtraLinkInEntry(String rel, String type, String href, Entry entry) + throws XmlPullParserException, IOException { + if (LINK_REL_PHOTO.equals(rel)) { + ContactEntry contactEntry = (ContactEntry) entry; + XmlPullParser parser = getParser(); + String etag = parser.getAttributeValue(NAMESPACE_GD_URI, XmlNametable.ETAG); + contactEntry.setLinkPhoto(href, type, etag); + } + } + + private static void parseContactsElement(ContactsElement element, XmlPullParser parser, + Hashtable relToTypeMap) throws XmlPullParserException { + parseTypedElement(element, parser, relToTypeMap); + element.setIsPrimary("true".equals(parser.getAttributeValue(null /* ns */, XmlNametable.PRIMARY))); + } + + private static void parseTypedElement(TypedElement element, XmlPullParser parser, + Hashtable relToTypeMap) throws XmlPullParserException { + String rel = parser.getAttributeValue(null /* ns */, XmlNametable.REL); + String label = parser.getAttributeValue(null /* ns */, XmlNametable.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) { + element.setType(relToType(rel, relToTypeMap)); + } + element.setLabel(label); + } + + private static byte relToType(String rel, Hashtable relToTypeMap) + throws XmlPullParserException { + if (rel != null) { + final Object type = relToTypeMap.get(rel.toLowerCase()); + if (type == null) { + throw new XmlPullParserException("unknown rel, " + rel); + } + return ((Byte) type).byteValue(); + } + return TypedElement.TYPE_NONE; + } + + 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 (XmlNametable.GD_ORG_NAME.equals(tag)) { + element.setName(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GD_ORG_TITLE.equals(tag)) { + element.setTitle(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GD_ORG_DEPARTMENT.equals(tag)) { + element.setOrgDepartment(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GD_ORG_JOBDESC.equals(tag)) { + element.setOrgJobDescription(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GD_ORG_SYMBOL.equals(tag)) { + element.setOrgSymbol(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GD_WHERE.equals(tag)) { + String where = parser.getAttributeValue(null /* ns */, + XmlNametable.VALUESTRING); + element.setWhere(where); + } + } + } + + + private static void handleEventSubElement(Event element, XmlPullParser parser) + throws XmlPullParserException, IOException { + int depth = parser.getDepth(); + while (true) { + String tag = XmlUtils.nextDirectChildTag(parser, depth); + if (tag == null) break; + if (XmlNametable.GD_WHEN.equals(tag)) { + String startDate = parser.getAttributeValue(null /* ns */, + XmlNametable.STARTTIME); + element.setStartDate(startDate); + } + } + } + + + private static void handleNameSubElement(Name element, XmlPullParser parser) + throws XmlPullParserException, IOException { + int depth = parser.getDepth(); + while (true) { + String tag = XmlUtils.nextDirectChildTag(parser, depth); + if (tag == null) break; + if (XmlNametable.GD_NAME_GIVENNAME.equals(tag)) { + element.setGivenName(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GD_NAME_ADDITIONALNAME.equals(tag)) { + element.setAdditionalNameYomi( + parser.getAttributeValue(null /* ns */, XmlNametable.GD_NAME_YOMI)); + element.setAdditionalName(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GD_NAME_FAMILYNAME.equals(tag)) { + element.setFamilyNameYomi( + parser.getAttributeValue(null /* ns */, XmlNametable.GD_NAME_YOMI)); + element.setFamilyName(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GD_NAME_PREFIX.equals(tag)) { + element.setNamePrefix(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GD_NAME_SUFFIX.equals(tag)) { + element.setNameSuffix(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GD_NAME_FULLNAME.equals(tag)) { + element.setFullName(XmlUtils.extractChildText(parser)); + } + } + } + + private static void handleStructuredPostalAddressSubElement( + StructuredPostalAddress element, XmlPullParser parser) + throws XmlPullParserException, IOException { + int depth = parser.getDepth(); + while (true) { + String tag = XmlUtils.nextDirectChildTag(parser, depth); + if (tag == null) break; + if (XmlNametable.GD_SPA_STREET.equals(tag)) { + element.setStreet(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GD_SPA_POBOX.equals(tag)) { + element.setPobox(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GD_SPA_NEIGHBORHOOD.equals(tag)) { + element.setNeighborhood(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GD_SPA_CITY.equals(tag)) { + element.setCity(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GD_SPA_REGION.equals(tag)) { + element.setRegion(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GD_SPA_POSTCODE.equals(tag)) { + element.setPostcode(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GD_SPA_COUNTRY.equals(tag)) { + element.setCountry(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.GD_SPA_FORMATTEDADDRESS.equals(tag)) { + element.setFormattedAddress(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 */, XmlNametable.GD_NAME)); + extendedProperty.setValue(parser.getAttributeValue(null /* ns */, XmlNametable.VALUE)); + extendedProperty.setXmlBlob(XmlUtils.extractFirstChildTextIgnoreRest(parser)); + } +} diff --git a/src/com/google/wireless/gdata2/contacts/parser/xml/XmlContactsGDataParserFactory.java b/src/com/google/wireless/gdata2/contacts/parser/xml/XmlContactsGDataParserFactory.java new file mode 100644 index 0000000..7439966 --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/parser/xml/XmlContactsGDataParserFactory.java @@ -0,0 +1,137 @@ +// Copyright 2007 The Android Open Source Project + +package com.google.wireless.gdata2.contacts.parser.xml; + +import com.google.wireless.gdata2.client.GDataParserFactory; +import com.google.wireless.gdata2.contacts.data.ContactEntry; +import com.google.wireless.gdata2.contacts.data.GroupEntry; +import com.google.wireless.gdata2.data.MediaEntry; +import com.google.wireless.gdata2.contacts.serializer.xml.XmlContactEntryGDataSerializer; +import com.google.wireless.gdata2.contacts.serializer.xml.XmlGroupEntryGDataSerializer; +import com.google.wireless.gdata2.data.Entry; +import com.google.wireless.gdata2.parser.GDataParser; +import com.google.wireless.gdata2.parser.ParseException; +import com.google.wireless.gdata2.parser.xml.XmlParserFactory; +import com.google.wireless.gdata2.parser.xml.XmlMediaEntryGDataParser; +import com.google.wireless.gdata2.serializer.GDataSerializer; +import com.google.wireless.gdata2.serializer.xml.XmlBatchGDataSerializer; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.InputStream; +import java.util.Enumeration; + +/** + * 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.gdata2.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.gdata2.client.GDataParserFactory#createParser( + * int, java.io.InputStream) + */ + public GDataParser createParser(Class entryClass, InputStream is) + throws ParseException { + if (entryClass == ContactEntry.class) { + return createParser(is); + } + if (entryClass == GroupEntry.class) { + return createGroupEntryFeedParser(is); + } + if (entryClass == MediaEntry.class) { + return createMediaEntryFeedParser(is); + } + throw new IllegalArgumentException("unexpected feed type, " + entryClass.getName()); + } + + /** + * Creates a new {@link GDataSerializer} for the provided entry. The entry + * <strong>must</strong> be an instance of {@link ContactEntry} or {@link GroupEntry}. + * + * @param entry The {@link ContactEntry} that should be serialized. + * @return The {@link GDataSerializer} that will serialize this entry. + * @throws IllegalArgumentException Thrown if entry is not a + * {@link ContactEntry} or {@link GroupEntry}. + * @see com.google.wireless.gdata2.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()); + } + + /** + * Creates a new {@link GDataSerializer} for the given batch. + * + * @param batch the {@link Enumeration} of entries in the batch. + * @return The {@link GDataSerializer} that will serialize this batch. + */ + public GDataSerializer createSerializer(Enumeration batch) { + return new XmlBatchGDataSerializer(this, xmlFactory, batch); + } +} diff --git a/src/com/google/wireless/gdata2/contacts/parser/xml/XmlGroupEntryGDataParser.java b/src/com/google/wireless/gdata2/contacts/parser/xml/XmlGroupEntryGDataParser.java new file mode 100644 index 0000000..943eb41 --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/parser/xml/XmlGroupEntryGDataParser.java @@ -0,0 +1,62 @@ +// Copyright 2009 The Android Open Source Project + +package com.google.wireless.gdata2.contacts.parser.xml; + +import com.google.wireless.gdata2.contacts.data.GroupEntry; +import com.google.wireless.gdata2.contacts.data.GroupsFeed; +import com.google.wireless.gdata2.data.Entry; +import com.google.wireless.gdata2.data.Feed; +import com.google.wireless.gdata2.data.StringUtils; +import com.google.wireless.gdata2.parser.ParseException; +import com.google.wireless.gdata2.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.gdata2.parser.xml.XmlGDataParser#createFeed() + */ + protected Feed createFeed() { + return new GroupsFeed(); + } + + /* + * (non-Javadoc) + * @see com.google.wireless.gdata2.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/gdata2/contacts/parser/xml/XmlNametable.java b/src/com/google/wireless/gdata2/contacts/parser/xml/XmlNametable.java new file mode 100644 index 0000000..44c3219 --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/parser/xml/XmlNametable.java @@ -0,0 +1,83 @@ +// Copyright 2009 The Android Open Source Project + +package com.google.wireless.gdata2.contacts.parser.xml; + +/** + * Class to hold static strings that are used to parse and + * serialize the xml elements in the contacts feed + */ +public final class XmlNametable { + private XmlNametable() {} + + // generic strings for attributes + public static String HREF = "href"; + public static String LABEL = "label"; + public static String VALUE = "value"; + public static String CODE = "code"; + public static String REL = "rel"; + public static String KEY = "key"; + public static String ETAG = "etag"; + public static String VALUESTRING = "valueString"; + public static String STARTTIME = "startTime"; + public static String PRIMARY = "primary"; + + // GD namespace + public static String GD_EMAIL = "email"; + public static String GD_EMAIL_DISPLAYNAME = "displayName"; + public static String GD_ADDRESS = "address"; + public static String GD_PROTOCOL = "protocol"; + public static String GD_IM = "im"; + public static String GD_DELETED = "deleted"; + public static String GD_NAME = "name"; + public static String GD_NAME_GIVENNAME = "givenName"; + public static String GD_NAME_ADDITIONALNAME = "additionalName"; + public static String GD_NAME_YOMI = "yomi"; + public static String GD_NAME_FAMILYNAME = "familyName"; + public static String GD_NAME_PREFIX = "namePrefix"; + public static String GD_NAME_SUFFIX = "nameSuffix"; + public static String GD_NAME_FULLNAME = "fullName"; + public static String GD_SPA = "structuredPostalAddress"; + public static String GD_SPA_STREET = "street"; + public static String GD_SPA_POBOX = "pobox"; + public static String GD_SPA_NEIGHBORHOOD = "neighborhood"; + public static String GD_SPA_CITY = "city"; + public static String GD_SPA_REGION = "region"; + public static String GD_SPA_POSTCODE = "postcode"; + public static String GD_SPA_COUNTRY = "country"; + public static String GD_SPA_FORMATTEDADDRESS = "formattedAddress"; + public static String GD_PHONENUMBER = "phoneNumber"; + public static String GD_ORGANIZATION = "organization"; + public static String GD_EXTENDEDPROPERTY = "extendedProperty"; + public static String GD_WHEN = "when"; + public static String GD_WHERE = "where"; + public static String GD_ORG_NAME = "orgName"; + public static String GD_ORG_TITLE = "orgTitle"; + public static String GD_ORG_DEPARTMENT = "orgDepartment"; + public static String GD_ORG_JOBDESC = "orgJobDescription"; + public static String GD_ORG_SYMBOL = "orgSymbol"; + + // Contacts namespace + public static String GC_GMI = "groupMembershipInfo"; + public static String GC_BIRTHDAY = "birthday"; + public static String GC_BILLINGINFO = "billingInformation"; + public static String GC_CALENDARLINK = "calendarLink"; + public static String GC_DIRECTORYSERVER = "directoryServer"; + public static String GC_EVENT = "event"; + public static String GC_EXTERNALID = "externalId"; + public static String GC_GENDER = "gender"; + public static String GC_HOBBY = "hobby"; + public static String GC_INITIALS = "initials"; + public static String GC_JOT = "jot"; + public static String GC_LANGUAGE = "language"; + public static String GC_MAIDENNAME = "maidenName"; + public static String GC_MILEAGE = "mileage"; + public static String GC_NICKNAME = "nickname"; + public static String GC_OCCUPATION = "occupation"; + public static String GC_PRIORITY = "priority"; + public static String GC_RELATION = "relation"; + public static String GC_SENSITIVITY = "sensitivity"; + public static String GC_SHORTNAME = "shortName"; + public static String GC_SUBJECT = "subject"; + public static String GC_UDF = "userDefinedField"; + public static String GC_WEBSITE ="website"; +}
\ No newline at end of file diff --git a/src/com/google/wireless/gdata/spreadsheets/serializer/package.html b/src/com/google/wireless/gdata2/contacts/parser/xml/package.html index 1c9bf9d..1c9bf9d 100644 --- a/src/com/google/wireless/gdata/spreadsheets/serializer/package.html +++ b/src/com/google/wireless/gdata2/contacts/parser/xml/package.html diff --git a/src/com/google/wireless/gdata/spreadsheets/serializer/xml/package.html b/src/com/google/wireless/gdata2/contacts/serializer/package.html index 1c9bf9d..1c9bf9d 100644 --- a/src/com/google/wireless/gdata/spreadsheets/serializer/xml/package.html +++ b/src/com/google/wireless/gdata2/contacts/serializer/package.html diff --git a/src/com/google/wireless/gdata2/contacts/serializer/xml/XmlContactEntryGDataSerializer.java b/src/com/google/wireless/gdata2/contacts/serializer/xml/XmlContactEntryGDataSerializer.java new file mode 100644 index 0000000..78e898a --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/serializer/xml/XmlContactEntryGDataSerializer.java @@ -0,0 +1,557 @@ +// Copyright 2009 The Android Open Source Project + +package com.google.wireless.gdata2.contacts.serializer.xml; + +import com.google.wireless.gdata2.contacts.data.CalendarLink; +import com.google.wireless.gdata2.contacts.data.ContactEntry; +import com.google.wireless.gdata2.contacts.data.ContactsElement; +import com.google.wireless.gdata2.contacts.data.EmailAddress; +import com.google.wireless.gdata2.contacts.data.Event; +import com.google.wireless.gdata2.contacts.data.ExternalId; +import com.google.wireless.gdata2.contacts.data.GroupMembershipInfo; +import com.google.wireless.gdata2.contacts.data.ImAddress; +import com.google.wireless.gdata2.contacts.data.Jot; +import com.google.wireless.gdata2.contacts.data.Language; +import com.google.wireless.gdata2.contacts.data.Name; +import com.google.wireless.gdata2.contacts.data.Organization; +import com.google.wireless.gdata2.contacts.data.PhoneNumber; +import com.google.wireless.gdata2.contacts.data.Relation; +import com.google.wireless.gdata2.contacts.data.StructuredPostalAddress; +import com.google.wireless.gdata2.contacts.data.TypedElement; +import com.google.wireless.gdata2.contacts.data.UserDefinedField; +import com.google.wireless.gdata2.contacts.data.WebSite; +import com.google.wireless.gdata2.contacts.parser.xml.XmlContactsGDataParser; +import com.google.wireless.gdata2.contacts.parser.xml.XmlNametable; +import com.google.wireless.gdata2.data.ExtendedProperty; +import com.google.wireless.gdata2.data.StringUtils; +import com.google.wireless.gdata2.parser.ParseException; +import com.google.wireless.gdata2.parser.xml.XmlGDataParser; +import com.google.wireless.gdata2.parser.xml.XmlParserFactory; +import com.google.wireless.gdata2.serializer.xml.XmlEntryGDataSerializer; + +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; + +import org.xmlpull.v1.XmlSerializer; + + +/** + * Serializes Google Contact entries into the Atom XML format. + */ +public class XmlContactEntryGDataSerializer extends XmlEntryGDataSerializer { + + public XmlContactEntryGDataSerializer(XmlParserFactory factory, ContactEntry entry) { + super(factory, entry); + } + + 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_PHOTO, + entry.getLinkPhotoHref(), entry.getLinkPhotoType(), entry.getLinkPhotoETag()); + + 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, (StructuredPostalAddress) 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()); + } + + Enumeration eachCalendar = entry.getCalendarLinks().elements(); + while (eachCalendar.hasMoreElements()) { + serialize(serializer, (CalendarLink) eachCalendar.nextElement()); + } + + Enumeration eachEvent = entry.getEvents().elements(); + while (eachEvent.hasMoreElements()) { + serialize(serializer, (Event) eachEvent.nextElement()); + } + + Enumeration eachWebsite = entry.getWebSites().elements(); + while (eachWebsite.hasMoreElements()) { + serialize(serializer, (WebSite) eachWebsite.nextElement()); + } + + Enumeration eachExternalId = entry.getExternalIds().elements(); + while (eachExternalId.hasMoreElements()) { + serialize(serializer, (ExternalId) eachExternalId.nextElement()); + } + + Enumeration eachHobby = entry.getHobbies().elements(); + while (eachHobby.hasMoreElements()) { + serializeHobby(serializer, (String) eachHobby.nextElement()); + } + + Enumeration eachJot = entry.getJots().elements(); + while (eachJot.hasMoreElements()) { + serialize(serializer, (Jot) eachJot.nextElement()); + } + + Enumeration eachLanguage = entry.getLanguages().elements(); + while (eachLanguage.hasMoreElements()) { + serialize(serializer, (Language) eachLanguage.nextElement()); + } + + Enumeration eachRelation = entry.getRelations().elements(); + while (eachRelation.hasMoreElements()) { + serialize(serializer, (Relation) eachRelation.nextElement()); + } + + Enumeration eachUDF = entry.getUserDefinedFields().elements(); + while (eachUDF.hasMoreElements()) { + serialize(serializer, (UserDefinedField) eachUDF.nextElement()); + } + + serializeBirthday(serializer, entry.getBirthday()); + + // now serialize simple properties + + serializeElement(serializer, entry.getDirectoryServer(), XmlNametable.GC_DIRECTORYSERVER); + serializeGenderElement(serializer, entry.getGender()); + serializeElement(serializer, entry.getInitials(), XmlNametable.GC_INITIALS); + serializeElement(serializer, entry.getMaidenName(), XmlNametable.GC_MAIDENNAME); + serializeElement(serializer, entry.getMileage(), XmlNametable.GC_MILEAGE); + serializeElement(serializer, entry.getNickname(), XmlNametable.GC_NICKNAME); + serializeElement(serializer, entry.getOccupation(), XmlNametable.GC_OCCUPATION); + serializeElement(serializer, entry.getShortName(), XmlNametable.GC_SHORTNAME); + serializeElement(serializer, entry.getSubject(), XmlNametable.GC_SUBJECT); + serializeElement(serializer, entry.getBillingInformation(), XmlNametable.GC_BILLINGINFO); + serializeElement(serializer, entry.getPriority(), + XmlNametable.GC_PRIORITY, XmlContactsGDataParser.TYPE_TO_REL_PRIORITY); + serializeElement(serializer, entry.getSensitivity(), + XmlNametable.GC_SENSITIVITY, XmlContactsGDataParser.TYPE_TO_REL_SENSITIVITY); + + serializeName(serializer, entry.getName()); + } + + private static void serialize(XmlSerializer serializer, EmailAddress email) + throws IOException, ParseException { + if (StringUtils.isEmptyOrWhitespace(email.getAddress())) return; + serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_EMAIL); + serializeContactsElement(serializer, email, XmlContactsGDataParser.TYPE_TO_REL_EMAIL); + serializer.attribute(null /* ns */, XmlNametable.GD_ADDRESS, email.getAddress()); + serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_EMAIL); + } + + private static void serialize(XmlSerializer serializer, ImAddress im) + throws IOException, ParseException { + if (StringUtils.isEmptyOrWhitespace(im.getAddress())) return; + + serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_IM); + serializeContactsElement(serializer, im, XmlContactsGDataParser.TYPE_TO_REL_IM); + serializer.attribute(null /* ns */, XmlNametable.GD_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 */, XmlNametable.GD_PROTOCOL, protocolString); + break; + + default: + protocolString = (String)XmlContactsGDataParser.IM_PROTOCOL_TYPE_TO_STRING_MAP.get( + new Byte(im.getProtocolPredefined())); + serializer.attribute(null /* ns */, XmlNametable.GD_PROTOCOL, protocolString); + break; + } + + serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_IM); + } + + private static void serialize(XmlSerializer serializer, PhoneNumber phone) + throws IOException, ParseException { + if (StringUtils.isEmptyOrWhitespace(phone.getPhoneNumber())) return; + serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_PHONENUMBER); + serializeContactsElement(serializer, phone, XmlContactsGDataParser.TYPE_TO_REL_PHONE); + serializer.text(phone.getPhoneNumber()); + serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_PHONENUMBER); + } + + private static void serialize(XmlSerializer serializer, Organization organization) + throws IOException, ParseException { + + serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_ORGANIZATION); + serializeContactsElement(serializer, + organization, XmlContactsGDataParser.TYPE_TO_REL_ORGANIZATION); + serializeGDSubelement(serializer, organization.getName(), + XmlNametable.GD_ORG_NAME); + serializeGDSubelement(serializer, organization.getTitle(), + XmlNametable.GD_ORG_TITLE); + serializeGDSubelement(serializer, organization.getOrgDepartment(), + XmlNametable.GD_ORG_DEPARTMENT); + serializeGDSubelement(serializer, organization.getOrgJobDescription(), + XmlNametable.GD_ORG_JOBDESC); + serializeGDSubelement(serializer, organization.getOrgSymbol(), + XmlNametable.GD_ORG_SYMBOL); + + final String where = organization.getWhere(); + if (!StringUtils.isEmpty(where)) { + serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_WHERE); + serializer.attribute(null /* ns */, XmlNametable.VALUESTRING, where); + serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_WHERE); + } + + serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_ORGANIZATION); + } + + + /** + * Gets called out of the main serializer loop. Parameters are + * not null. + * + * @param serializer + * @param addr + */ + private static void serialize(XmlSerializer serializer, StructuredPostalAddress addr) + throws IOException, ParseException { + serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_SPA); + serializeContactsElement(serializer, addr, XmlContactsGDataParser.TYPE_TO_REL_POSTAL); + serializeGDSubelement(serializer, addr.getStreet(), XmlNametable.GD_SPA_STREET); + serializeGDSubelement(serializer, addr.getPobox(), XmlNametable.GD_SPA_POBOX); + serializeGDSubelement(serializer, addr.getNeighborhood(), XmlNametable.GD_SPA_NEIGHBORHOOD); + serializeGDSubelement(serializer, addr.getCity(), XmlNametable.GD_SPA_CITY); + serializeGDSubelement(serializer, addr.getRegion(), XmlNametable.GD_SPA_REGION); + serializeGDSubelement(serializer, addr.getPostcode(), XmlNametable.GD_SPA_POSTCODE); + serializeGDSubelement(serializer, addr.getCountry(), XmlNametable.GD_SPA_COUNTRY); + serializeGDSubelement(serializer, addr.getFormattedAddress(), + XmlNametable.GD_SPA_FORMATTEDADDRESS); + serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_SPA); + } + + private static void serializeGDSubelement(XmlSerializer serializer, String value, + String elementName) + throws IOException { + if (StringUtils.isEmpty(value)) return; + serializer.startTag(XmlContactsGDataParser.NAMESPACE_GD_URI, elementName); + serializer.text(value); + serializer.endTag(XmlContactsGDataParser.NAMESPACE_GD_URI, elementName); + } + + + private static void serializeTypedElement(XmlSerializer serializer, TypedElement element, + Hashtable typeToRelMap) throws IOException, ParseException { + + final String label = element.getLabel(); + byte type = element.getType(); + boolean hasType = type != TypedElement.TYPE_NONE; + + // validate the element + element.validate(); + + if (label != null) { + serializer.attribute(null /* ns */, XmlNametable.LABEL, label); + } + if (hasType) { + serializeRelation(serializer, type, typeToRelMap); + } + } + + private static void serializeRelation(XmlSerializer serializer, byte type, + Hashtable typeToRelMap) throws IOException { + + serializer.attribute(null /* ns */, XmlNametable.REL, + (String)typeToRelMap.get(new Byte(type))); + } + + private static void serializeContactsElement(XmlSerializer serializer, ContactsElement element, + Hashtable typeToRelMap) throws IOException, ParseException { + serializeTypedElement(serializer, element, typeToRelMap); + if (element.isPrimary()) { + serializer.attribute(null /* ns */, XmlNametable.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, XmlNametable.GC_GMI); + serializer.attribute(null /* ns */, XmlNametable.HREF, group); + serializer.attribute(null /* ns */, XmlNametable.GD_DELETED, isDeleted ? "true" : "false"); + serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_GMI); + } + + private static void serialize(XmlSerializer serializer, ExtendedProperty extendedProperty) + throws IOException { + final String name = extendedProperty.getName(); + final String value = extendedProperty.getValue(); + final String xmlBlob = extendedProperty.getXmlBlob(); + + serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_EXTENDEDPROPERTY); + if (!StringUtils.isEmpty(name)) { + serializer.attribute(null /* ns */, XmlNametable.GD_NAME, name); + } + if (!StringUtils.isEmpty(value)) { + serializer.attribute(null /* ns */, XmlNametable.VALUE, value); + } + if (!StringUtils.isEmpty(xmlBlob)) { + serializeBlob(serializer, xmlBlob); + } + serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_EXTENDEDPROPERTY); + } + + private static void serializeBlob(XmlSerializer serializer, String blob) + throws IOException { + serializer.text(blob); + } + + /** + * takes a typed element and a string value and determines if + * the element and the string together should be serialized. + * if the string is non empty, or the typedelement is worthy of + * serialization, this will return true. + * + * @param element + * @param value + * + * @return boolean + */ + private static boolean shouldSerialize(TypedElement element, String value) + { + if (element.getType() != TypedElement.TYPE_NONE) { + return true; + } + if (!StringUtils.isEmptyOrWhitespace(element.getLabel())) { + return true; + } + if (!StringUtils.isEmptyOrWhitespace(value)) { + return true; + } + return false; + } + + private static void serialize(XmlSerializer serializer, CalendarLink calendarLink) + throws IOException, ParseException { + final String href = calendarLink.getHRef(); + + if (shouldSerialize(calendarLink, href)) { + serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, + XmlNametable.GC_CALENDARLINK); + serializeContactsElement(serializer, calendarLink, + XmlContactsGDataParser.TYPE_TO_REL_CALENDARLINK); + if (!StringUtils.isEmpty(href)) { + serializer.attribute(null /* ns */, XmlNametable.HREF, href); + } + serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, + XmlNametable.GC_CALENDARLINK); + } + } + + private static void serialize(XmlSerializer serializer, Event event) + throws IOException, ParseException { + final String startDate = event.getStartDate(); + if (shouldSerialize(event, startDate)) { + serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_EVENT); + serializeTypedElement(serializer, event, XmlContactsGDataParser.TYPE_TO_REL_EVENT); + if (!StringUtils.isEmpty(startDate)) { + serializer.startTag(XmlContactsGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_WHEN); + serializer.attribute(null /* ns */, XmlNametable.STARTTIME, startDate); + serializer.endTag(XmlContactsGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_WHEN); + } + serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_EVENT); + } + } + + private static void serialize(XmlSerializer serializer, ExternalId externalId) + throws IOException, ParseException { + final String value = externalId.getValue(); + if (shouldSerialize(externalId, value)) { + serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, + XmlNametable.GC_EXTERNALID); + serializeTypedElement(serializer, externalId, + XmlContactsGDataParser.TYPE_TO_REL_EXTERNALID); + if (!StringUtils.isEmpty(value)) { + serializer.attribute(null /* ns */, XmlNametable.VALUE, value); + } + serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, + XmlNametable.GC_EXTERNALID); + } + } + + private static void serializeHobby(XmlSerializer serializer, String hobby) + throws IOException { + if (StringUtils.isEmptyOrWhitespace(hobby)) return; + serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_HOBBY); + serializer.text(hobby); + serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_HOBBY); + } + + private static void serializeBirthday(XmlSerializer serializer, String birthday) + throws IOException { + if (StringUtils.isEmptyOrWhitespace(birthday)) return; + serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_BIRTHDAY); + serializer.attribute(null /* ns */, XmlNametable.GD_WHEN, birthday); + serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_BIRTHDAY); + } + + private static void serialize(XmlSerializer serializer, Jot jot) + throws IOException { + final String value = jot.getLabel(); + + if (!StringUtils.isEmptyOrWhitespace(value)) { + serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_JOT); + serializeRelation(serializer, jot.getType(), XmlContactsGDataParser.TYPE_TO_REL_JOT); + serializer.text(value); + serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_JOT); + } + } + + private static void serialize(XmlSerializer serializer, Language language) + throws IOException, ParseException { + language.validate(); + serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_LANGUAGE); + final String value = language.getCode(); + if (!StringUtils.isEmptyOrWhitespace(value)) { + serializer.attribute(null /* ns */, XmlNametable.CODE, value); + } else { + serializer.attribute(null /* ns */, XmlNametable.LABEL, language.getLabel()); + } + serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_LANGUAGE); + } + + private static void serialize(XmlSerializer serializer, Relation relation) + throws IOException, ParseException { + final String value = relation.getText(); + if (shouldSerialize(relation, value)) { + serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_RELATION); + serializeTypedElement(serializer, relation, XmlContactsGDataParser.TYPE_TO_REL_RELATION); + serializer.text(value); + serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_RELATION); + } + } + + private static void serialize(XmlSerializer serializer, UserDefinedField udf) + throws IOException, ParseException { + udf.validate(); + serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_UDF); + serializer.attribute(null /* ns */, XmlNametable.KEY, udf.getKey()); + serializer.attribute(null /* ns */, XmlNametable.VALUE, udf.getValue()); + serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_UDF); + } + + private static void serialize(XmlSerializer serializer, WebSite webSite) + throws IOException, ParseException { + final String href = webSite.getHRef(); + + if (shouldSerialize(webSite, href)) { + serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_WEBSITE); + serializeContactsElement(serializer, webSite, XmlContactsGDataParser.TYPE_TO_REL_WEBSITE); + if (!StringUtils.isEmpty(href)) { + serializer.attribute(null /* ns */, XmlNametable.HREF, href); + } + serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_WEBSITE); + } + } + + private static void serializeElement(XmlSerializer serializer, String value, String elementName) + throws IOException { + if (StringUtils.isEmpty(value)) return; + serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, elementName); + serializer.text(value); + serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, elementName); + } + + private static void serializeGenderElement(XmlSerializer serializer, String value) + throws IOException { + if (StringUtils.isEmpty(value)) return; + serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_GENDER); + serializer.attribute(null /* ns */, XmlNametable.VALUE, value); + serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, XmlNametable.GC_GENDER); + } + + private static void serializeElement(XmlSerializer serializer, byte value, String elementName, + Hashtable typeToRelMap) throws IOException { + if (value == TypedElement.TYPE_NONE) return; + serializer.startTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, elementName); + serializeRelation(serializer, value, typeToRelMap); + serializer.endTag(XmlContactsGDataParser.NAMESPACE_CONTACTS_URI, elementName); + } + + private static void serializeName(XmlSerializer serializer, Name name) + throws IOException { + if (name == null) return; + serializer.startTag(XmlContactsGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_NAME); + serializeNameSubelement(serializer, name.getGivenName(), + name.getGivenNameYomi(), XmlNametable.GD_NAME_GIVENNAME); + serializeNameSubelement(serializer, name.getAdditionalName(), + name.getAdditionalNameYomi(), XmlNametable.GD_NAME_ADDITIONALNAME); + serializeNameSubelement(serializer, name.getFamilyName(), + name.getFamilyNameYomi(), XmlNametable.GD_NAME_FAMILYNAME); + serializeNameSubelement(serializer, name.getNamePrefix(), + null /* yomi */, XmlNametable.GD_NAME_PREFIX); + serializeNameSubelement(serializer, name.getNameSuffix(), + null /* yomi */, XmlNametable.GD_NAME_SUFFIX); + serializeNameSubelement(serializer, name.getFullName(), + null /* yomi */, XmlNametable.GD_NAME_FULLNAME); + serializer.endTag(XmlContactsGDataParser.NAMESPACE_GD_URI, XmlNametable.GD_NAME); + } + + private static void serializeNameSubelement(XmlSerializer serializer, String value, + String yomi, String elementName) + throws IOException { + if (StringUtils.isEmpty(value)) return; + serializer.startTag(XmlContactsGDataParser.NAMESPACE_GD_URI, elementName); + if (!StringUtils.isEmpty(yomi)) { + serializer.attribute(null /* ns */, XmlNametable.GD_NAME_YOMI, yomi); + } + serializer.text(value); + serializer.endTag(XmlContactsGDataParser.NAMESPACE_GD_URI, elementName); + } +} diff --git a/src/com/google/wireless/gdata2/contacts/serializer/xml/XmlGroupEntryGDataSerializer.java b/src/com/google/wireless/gdata2/contacts/serializer/xml/XmlGroupEntryGDataSerializer.java new file mode 100644 index 0000000..52d955b --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/serializer/xml/XmlGroupEntryGDataSerializer.java @@ -0,0 +1,54 @@ +// Copyright 2009 The Android Open Source Project + +package com.google.wireless.gdata2.contacts.serializer.xml; + +import com.google.wireless.gdata2.contacts.data.GroupEntry; +import com.google.wireless.gdata2.contacts.parser.xml.XmlContactsGDataParser; +import com.google.wireless.gdata2.parser.xml.XmlParserFactory; +import com.google.wireless.gdata2.parser.ParseException; +import com.google.wireless.gdata2.serializer.xml.XmlEntryGDataSerializer; +import com.google.wireless.gdata2.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(); + } + + 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/gdata2/contacts/serializer/xml/package.html b/src/com/google/wireless/gdata2/contacts/serializer/xml/package.html new file mode 100644 index 0000000..1c9bf9d --- /dev/null +++ b/src/com/google/wireless/gdata2/contacts/serializer/xml/package.html @@ -0,0 +1,5 @@ +<html> +<body> + {@hide} +</body> +</html> diff --git a/src/com/google/wireless/gdata2/data/Entry.java b/src/com/google/wireless/gdata2/data/Entry.java new file mode 100644 index 0000000..f0ac78a --- /dev/null +++ b/src/com/google/wireless/gdata2/data/Entry.java @@ -0,0 +1,401 @@ +// Copyright 2007 The Android Open Source Project + +package com.google.wireless.gdata2.data; + +import com.google.wireless.gdata2.data.batch.BatchInfo; +import com.google.wireless.gdata2.parser.ParseException; + +/** + * Entry in a GData feed. This is the rough equivalent of the atom:Entry + * element. The "atom:entry" element represents an individual entry, + * acting as a container for metadata and data associated with the + * entry. This element can appear as a child of the atom:feed element, + * or it can appear as the document (i.e., top-level) element of a + * standalone Atom Entry Document. + * The Entry class serves as a base class for Google service specific subclasses, + * like a contact or a calendar event. As a base class it takes care of the default + * attributes and elements that are common to all entries. + */ +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 String eTagValue = null; + private boolean deleted = false; + private BatchInfo batchInfo = null; + private String fields = null; + private String contentSource = null; + private String contentType = null; + + /** + * 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; + contentType = null; + contentSource = null; + author = null; + email = null; + category = null; + categoryScheme = null; + publicationDate = null; + updateDate = null; + deleted = false; + batchInfo = null; + } + + /** + * @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 this.content; + } + + /** + * @param content the content to set + */ + public void setContent(String content) { + this.content = content; + } + + /** + * @return the contents type, either one of the default + * types (text, html, xhtml) or an atomMediaType + */ + public String getContentType() { + return contentType; + } + + /** + * @param type the contentType to set + */ + public void setContentType(String type) { + this.contentType = type; + } + + /** + * If the content itself is empty, the src attribute + * points to the internet resource where the content + * can be loaded from + * @return the src attribute of the content element + */ + public String getContentSource() { + return contentSource; + } + + /** + * @param contentSource the url value to set + */ + public void setContentSource(String contentSource) { + this.contentSource = contentSource; + } + + + + /** + * @return the editUri + */ + public String getEditUri() { + return editUri; + } + + /** + * Note that setting the editUri is only valid during parsing + * time, this is a server generated value and can not be changed + * by the client normally + * @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; + } + + /** + * Note that setting the ID is only valid during parsing time, + * an ID is a server generated value and can not be changed by + * the client normally + * @param id the id to set + */ + public void setId(String id) { + this.id = id; + } + + /** + * @return the publicationDate + */ + public String getPublicationDate() { + return publicationDate; + } + + /** + * Note that setting the publicationDate is only valid during + * parsing time, this is a server generated value and can not be + * changed by the client normally + * @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; + } + + /** + * Note that setting the updateDate is only valid during parsing + * time, this is a server generated value and can not be changed + * by the client normally + * @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; + } + + /** + * @return the value of the parsed eTag attribute + */ + public String getETag() { + return eTagValue; + } + + /** + * Note that setting the etag is only valid during parsing + * time, this is a server generated value and can not be changed + * by the client normally + * @param eTag the eTag on the entry + */ + public void setETag(String eTag) { + eTagValue = eTag; + } + + /** + * @return the value of the parsed fields attribute + */ + public String getFields() { + return fields; + } + + /** + * @param fields the fields expression on the entry, used during serialization + */ + public void setFields(String fields) { + this.fields = fields; + } + + /** + * Used internally to access batch related properties. + * Clients should use {@link com.google.wireless.gdata2.data.batch.BatchUtils} instead. + */ + public BatchInfo getBatchInfo() { + return batchInfo; + } + + /** + * Used internally to update batch related properties. + * Clients should use {@link com.google.wireless.gdata2.data.batch.BatchUtils} instead. + */ + public void setBatchInfo(BatchInfo batchInfo) { + this.batchInfo = batchInfo; + } + + /** + * Appends the name and value to this StringBuffer, if value is not null. + * Uses the format: "<NAME>: <VALUE>\n" + * @param sb The StringBuffer in which the name and value should be + * appended. + * @param name The name that should be appended. + * @param value The value that should be appended. + */ + protected void appendIfNotNull(StringBuffer sb, + String name, String value) { + if (!StringUtils.isEmpty(value)) { + sb.append(name); + sb.append(": "); + sb.append(value); + sb.append("\n"); + } + } + + /** + * Helper method that creates the String representation of this Entry. + * Called by {@link #toString()}. + * Subclasses can add additional data to the StringBuffer. + * @param sb The StringBuffer that should be modified to add to the String + * representation of this Entry. + */ + protected void toString(StringBuffer sb) { + appendIfNotNull(sb, "ID", id); + appendIfNotNull(sb, "TITLE", title); + appendIfNotNull(sb, "EDIT URI", editUri); + appendIfNotNull(sb, "HTML URI", htmlUri); + appendIfNotNull(sb, "SUMMARY", summary); + appendIfNotNull(sb, "CONTENT", content); + appendIfNotNull(sb, "AUTHOR", author); + appendIfNotNull(sb, "CATEGORY", category); + appendIfNotNull(sb, "CATEGORY SCHEME", categoryScheme); + appendIfNotNull(sb, "PUBLICATION DATE", publicationDate); + appendIfNotNull(sb, "UPDATE DATE", updateDate); + appendIfNotNull(sb, "DELETED", String.valueOf(deleted)); + appendIfNotNull(sb, "ETAG", String.valueOf(eTagValue)); + if (batchInfo != null) { + appendIfNotNull(sb, "BATCH", batchInfo.toString()); + } + } + + /** + * 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/gdata2/data/ExtendedProperty.java b/src/com/google/wireless/gdata2/data/ExtendedProperty.java new file mode 100644 index 0000000..1e8d060 --- /dev/null +++ b/src/com/google/wireless/gdata2/data/ExtendedProperty.java @@ -0,0 +1,95 @@ +// Copyright 2009 The Android Open Source Project + +package com.google.wireless.gdata2.data; + +import com.google.wireless.gdata2.parser.ParseException; + +/** + * The extendedProperty gdata type + * Allows you to store a limited amount of custom data as an auxiliary + * property of the enclosing entity. + * Note that the presence of anyForeignElement allows feed to optionally + * embed any valid XML within gd:extendedProperty element + * (mutually exclusive with value attribute). + */ +public class ExtendedProperty { + private String name; + private String value; + private String xmlBlob; + + public ExtendedProperty() {} + public ExtendedProperty(String name, String value, String xmlBlob) { + this.name = name; + this.value = value; + this.xmlBlob = xmlBlob; + } + + /** + * Returns the xml embedded inside the extended property + * element + * Mutually exclusive with the value property + * @return the xml as a string + */ + public String getXmlBlob() { + return xmlBlob; + } + + /** + * Sets the embedded xml for the extended property element. + * Mutually exclusive with the value property + * @param xmlBlob xml as a string + */ + public void setXmlBlob(String xmlBlob) { + this.xmlBlob = xmlBlob; + } + + /** + * @return the name of the extended property expressed as a URI. Extended + * property URIs usually follow the {scheme}#{local-name} convention. + */ + public String getName() { + return name; + } + + /** + * @param name set's the name of the extended property + */ + public void setName(String name) { + this.name = name; + } + + /** + * Returns the value attribute of the extended property + * element.Mutually exclusive with the xmlBlob property + * @return the value + */ + public String getValue() { + return value; + } + + /** + * Sets the value attribute of the extended property. Mutually + * exclusive with the xmlBlog property + * @param 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/gdata2/data/Feed.java b/src/com/google/wireless/gdata2/data/Feed.java new file mode 100644 index 0000000..76c9e8a --- /dev/null +++ b/src/com/google/wireless/gdata2/data/Feed.java @@ -0,0 +1,173 @@ +// Copyright 2007 The Android Open Source Project + +package com.google.wireless.gdata2.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. + */ +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; + private String eTagValue; + + /** + * Creates a new, empty feed. + */ + public Feed() { + } + + /** + * The approximate number of total results in the feed + * @return totalResults + */ + public int getTotalResults() { + return totalResults; + } + + /** + * The number of toal results in the feed Only used during + * parsing of the feed + * @param totalResults + */ + public void setTotalResults(int totalResults) { + this.totalResults = totalResults; + } + + /** + * The starting Index of the current result set + * @return startIndex + */ + public int getStartIndex() { + return startIndex; + } + + /** + * The starting index of the current result set Only used during + * parsing of the feed + * @param startIndex + */ + public void setStartIndex(int startIndex) { + this.startIndex = startIndex; + } + + /** + * The number of items per Page in this result set + * @return itemsPerPage + */ + public int getItemsPerPage() { + return itemsPerPage; + } + + /** + * The number of items per page in this result set + * Only used during parsing of the feed + * @param itemsPerPage + */ + public void setItemsPerPage(int itemsPerPage) { + this.itemsPerPage = itemsPerPage; + } + + /** + * @return the category + */ + public String getCategory() { + return category; + } + + /** + * The category to set + * Only used during parsing of the feed + * @param category + */ + public void setCategory(String category) { + this.category = category; + } + + /** + * @return the categoryScheme + */ + public String getCategoryScheme() { + return categoryScheme; + } + + /** + * The categoryScheme to set + * Only used during parsing of the feed + * @param categoryScheme + */ + public void setCategoryScheme(String categoryScheme) { + this.categoryScheme = categoryScheme; + } + + /** + * @return the id + */ + public String getId() { + return id; + } + + /** + * the id to set + * Only used during parsing of the feed + * @param id + */ + public void setId(String id) { + this.id = id; + } + + /** + * @return the lastUpdated + */ + public String getLastUpdated() { + return lastUpdated; + } + + /** + * The lastUpdated to set + * Only used during parsing of the feed + * @param lastUpdated + */ + public void setLastUpdated(String lastUpdated) { + this.lastUpdated = lastUpdated; + } + + /** + * @return the title + */ + public String getTitle() { + return title; + } + + /** + * the title to set + * Only used during parsing of the feed + * @param title + */ + public void setTitle(String title) { + this.title = title; + } + + /** + * @return the value of the parsed eTag attribute + */ + public String getETag() { + return eTagValue; + } + + /** + * @param sets the eTag on the entry, used during + * parsing + */ + public void setETag(String eTag) { + eTagValue = eTag; + } + +} diff --git a/src/com/google/wireless/gdata2/data/MediaEntry.java b/src/com/google/wireless/gdata2/data/MediaEntry.java new file mode 100644 index 0000000..851fffb --- /dev/null +++ b/src/com/google/wireless/gdata2/data/MediaEntry.java @@ -0,0 +1,12 @@ +// Copyright 2009 The Android Open Source Project + +package com.google.wireless.gdata2.data; + +/** + * Entry containing information about media entries + */ +public class MediaEntry extends Entry { + public MediaEntry() { + super(); + } +} diff --git a/src/com/google/wireless/gdata2/data/StringUtils.java b/src/com/google/wireless/gdata2/data/StringUtils.java new file mode 100644 index 0000000..5cefb83 --- /dev/null +++ b/src/com/google/wireless/gdata2/data/StringUtils.java @@ -0,0 +1,78 @@ +// Copyright 2007 The Android Open Source Project + +package com.google.wireless.gdata2.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 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 (!isWhitespace(string.charAt(i))) { + return false; + } + } + return true; + } + + /** + * Converts a string into an integer. Uses the default value + * passed in if the string can not be converted + * @param string The String that should be converted to an integer + * @param defaultValue The default value that should be used if + * the string is not convertable + * @return the integer value of the string or the default value + */ + public static int parseInt(String string, int defaultValue) { + if (string != null) { + try { + return Integer.parseInt(string); + } catch (NumberFormatException nfe) { + // ignore + } + } + return defaultValue; + } + + /** + * Tests if a character is a whitespace character + * Uses + * http://en.wikipedia.org/wiki/Whitespace_%28computer_science%29 + * as the algorithm. + * @param c A character to be tested. + * @return true if the character is a whitespace + */ + public static boolean isWhitespace(char c) { + return ('\u0009' <= c && c <= '\r') || c == '\u0020' || c == '\u0085' + || c == '\u00A0' || c == '\u1680' || c == '\u180E' + || ('\u2000' <= c && c <= '\u200A') || c == '\u2028' || c == '\u2029' + || c == '\u202F' || c == '\u205F' || c == '\u3000'; + } + +} diff --git a/src/com/google/wireless/gdata2/data/XmlUtils.java b/src/com/google/wireless/gdata2/data/XmlUtils.java new file mode 100644 index 0000000..a69d817 --- /dev/null +++ b/src/com/google/wireless/gdata2/data/XmlUtils.java @@ -0,0 +1,100 @@ +// Copyright 2007 The Android Open Source Project + +package com.google.wireless.gdata2.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(); + } + + /** + * Extracts the child text for the first child element in the pull parser. + * Other child elements will be discarded + * @param parser The XmlPullParser parsing an XML document. + * @return The child text for the first child 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 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); + } + + /** + * Returns the nextDirectChildTag + * @param parser The XmlPullParser parsing an XML document. + * @param parentDepth the depth in the hierachy of the parent node + * @return The child element + * @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 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); + } +} diff --git a/src/com/google/wireless/gdata2/data/batch/BatchInfo.java b/src/com/google/wireless/gdata2/data/batch/BatchInfo.java new file mode 100644 index 0000000..ef461d2 --- /dev/null +++ b/src/com/google/wireless/gdata2/data/batch/BatchInfo.java @@ -0,0 +1,30 @@ +// Copyright 2009 Google Inc. All Rights Reserved. + +package com.google.wireless.gdata2.data.batch; + +/** + * Opaque container for batch related info associated with an entry. + * Clients should use {@link BatchUtils} to access this data instead. + */ +public class BatchInfo { + String id; + String operation; + BatchStatus status; + BatchInterrupted interrupted; + + /* package */ BatchInfo() { + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("id: ").append(id); + sb.append(" op: ").append(operation); + if (status != null) { + sb.append(" sc: ").append(status.getStatusCode()); + } + if (interrupted != null) { + sb.append(" interrupted: ").append(interrupted.getReason()); + } + return sb.toString(); + } +} diff --git a/src/com/google/wireless/gdata2/data/batch/BatchInterrupted.java b/src/com/google/wireless/gdata2/data/batch/BatchInterrupted.java new file mode 100644 index 0000000..ff8a69f --- /dev/null +++ b/src/com/google/wireless/gdata2/data/batch/BatchInterrupted.java @@ -0,0 +1,75 @@ +// Copyright 2009 Google Inc. All Rights Reserved. + +package com.google.wireless.gdata2.data.batch; + +/** + * Holds status information about a batch that was interrupted. + */ +public class BatchInterrupted { + private String reason; + private int total; + private int success; + private int error; + + /** + * Creates a new empty BatchInterrupted. + */ + public BatchInterrupted() { + } + + /** + * Returns the reason for this failure. + */ + public String getReason() { + return reason; + } + + /** + * Sets the reason for this failure. + */ + public void setReason(String reason) { + this.reason = reason; + } + + /** + * Gets the total number of entries read. + */ + public int getTotalCount() { + return total; + } + + /** + * Sets the number of entries read. + */ + public void setTotalCount(int total) { + this.total = total; + } + + /** + * Gets the number of entries that were processed successfully. + */ + public int getSuccessCount() { + return success; + } + + /** + * Sets the number of entries successfuly processed. + */ + public void setSuccessCount(int success) { + this.success = success; + } + + /** + * Gets the number of entries that were rejected. + */ + public int getErrorCount() { + return error; + } + + /** + * Sets the number of entries that failed. + */ + public void setErrorCount(int error) { + this.error = error; + } +} diff --git a/src/com/google/wireless/gdata2/data/batch/BatchStatus.java b/src/com/google/wireless/gdata2/data/batch/BatchStatus.java new file mode 100644 index 0000000..7a19afd --- /dev/null +++ b/src/com/google/wireless/gdata2/data/batch/BatchStatus.java @@ -0,0 +1,75 @@ +// Copyright 2009 Google Inc. All Rights Reserved. + +package com.google.wireless.gdata2.data.batch; + +/** + * Holds result status for an individual batch operation. + */ +public class BatchStatus { + private int statusCode; + private String reason; + private String contentType; + private String content; + + /** + * Creates a new empty BatchStatus. + */ + public BatchStatus() { + } + + /** + * Returns the status of this operation. + */ + public int getStatusCode() { + return statusCode; + } + + /** + * Sets the status of this operation. + */ + public void setStatusCode(int statusCode) { + this.statusCode = statusCode; + } + + /** + * Returns the reason for this status. + */ + public String getReason() { + return reason; + } + + /** + * Sets the reason for this status. + */ + public void setReason(String reason) { + this.reason = reason; + } + + /** + * Returns the content type of the response. + */ + public String getContentType() { + return contentType; + } + + /** + * Sets the content type of the response. + */ + public void setContentType(String contentType) { + this.contentType = contentType; + } + + /** + * Returns the response content, if any. + */ + public String getContent() { + return content; + } + + /** + * Sets the response content. + */ + public void setContent(String content) { + this.content = content; + } +} diff --git a/src/com/google/wireless/gdata2/data/batch/BatchUtils.java b/src/com/google/wireless/gdata2/data/batch/BatchUtils.java new file mode 100644 index 0000000..c237601 --- /dev/null +++ b/src/com/google/wireless/gdata2/data/batch/BatchUtils.java @@ -0,0 +1,93 @@ +// Copyright 2009 Google Inc. All Rights Reserved. + +package com.google.wireless.gdata2.data.batch; + +import com.google.wireless.gdata2.data.Entry; + +/** + * Utility methods for dealing with batch operations. + */ +public class BatchUtils { + + public static final String OPERATION_INSERT = "insert"; + + public static final String OPERATION_UPDATE = "update"; + + public static final String OPERATION_QUERY = "query"; + + public static final String OPERATION_DELETE = "delete"; + + private BatchUtils() { + } + + /** + * Returns the batch id of the given entry, or null if none. + */ + public static String getBatchId(Entry entry) { + BatchInfo info = entry.getBatchInfo(); + return info == null ? null : info.id; + } + + /** + * Sets the batch id of the given entry. + */ + public static void setBatchId(Entry entry, String id) { + getOrCreateBatchInfo(entry).id = id; + } + + /** + * Returns the batch operation of the given entry. + */ + public static String getBatchOperation(Entry entry) { + BatchInfo info = entry.getBatchInfo(); + return info == null ? null : info.operation; + } + + /** + * Sets the operation for the given batch entry. + */ + public static void setBatchOperation(Entry entry, String operation) { + getOrCreateBatchInfo(entry).operation = operation; + } + + /** + * Returns the status of the given batch entry. + */ + public static BatchStatus getBatchStatus(Entry entry) { + BatchInfo info = entry.getBatchInfo(); + return info == null ? null : info.status; + } + + /** + * Sets the status of the given batch entry. + */ + public static void setBatchStatus(Entry entry, BatchStatus status) { + getOrCreateBatchInfo(entry).status = status; + } + + /** + * Returns the interrupted status of the given entry, or null if none. + */ + public static BatchInterrupted getBatchInterrupted(Entry entry) { + BatchInfo info = entry.getBatchInfo(); + return info == null ? null : info.interrupted; + } + + /** + * Sets the interrupted status of the given entry. + */ + public static void setBatchInterrupted(Entry entry, + BatchInterrupted interrupted) { + getOrCreateBatchInfo(entry).interrupted = interrupted; + } + + private static BatchInfo getOrCreateBatchInfo(Entry entry) { + BatchInfo info = entry.getBatchInfo(); + if (info == null) { + info = new BatchInfo(); + entry.setBatchInfo(info); + } + return info; + } + +} diff --git a/src/com/google/wireless/gdata2/data/package.html b/src/com/google/wireless/gdata2/data/package.html new file mode 100644 index 0000000..1c9bf9d --- /dev/null +++ b/src/com/google/wireless/gdata2/data/package.html @@ -0,0 +1,5 @@ +<html> +<body> + {@hide} +</body> +</html> diff --git a/src/com/google/wireless/gdata2/package.html b/src/com/google/wireless/gdata2/package.html new file mode 100644 index 0000000..1c9bf9d --- /dev/null +++ b/src/com/google/wireless/gdata2/package.html @@ -0,0 +1,5 @@ +<html> +<body> + {@hide} +</body> +</html> diff --git a/src/com/google/wireless/gdata2/parser/GDataParser.java b/src/com/google/wireless/gdata2/parser/GDataParser.java new file mode 100644 index 0000000..ae3df73 --- /dev/null +++ b/src/com/google/wireless/gdata2/parser/GDataParser.java @@ -0,0 +1,64 @@ +// Copyright 2008 The Android Open Source Project + +package com.google.wireless.gdata2.parser; + +import com.google.wireless.gdata2.data.Entry; +import com.google.wireless.gdata2.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. + */ + Feed parseFeedEnvelope() 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/gdata2/parser/ParseException.java b/src/com/google/wireless/gdata2/parser/ParseException.java new file mode 100644 index 0000000..48786b4 --- /dev/null +++ b/src/com/google/wireless/gdata2/parser/ParseException.java @@ -0,0 +1,38 @@ +// Copyright 2007 The Android Open Source Project + +package com.google.wireless.gdata2.parser; + +import com.google.wireless.gdata2.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/gdata2/parser/package.html b/src/com/google/wireless/gdata2/parser/package.html new file mode 100644 index 0000000..1c9bf9d --- /dev/null +++ b/src/com/google/wireless/gdata2/parser/package.html @@ -0,0 +1,5 @@ +<html> +<body> + {@hide} +</body> +</html> diff --git a/src/com/google/wireless/gdata2/parser/xml/SimplePullParser.java b/src/com/google/wireless/gdata2/parser/xml/SimplePullParser.java new file mode 100644 index 0000000..7c573c9 --- /dev/null +++ b/src/com/google/wireless/gdata2/parser/xml/SimplePullParser.java @@ -0,0 +1,302 @@ +/* + * 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.gdata2.parser.xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** + * This is an abstraction of a pull parser that provides several benefits:<ul> + * <li>it is easier to use robustly because it makes it trivial to handle unexpected tags (which + * might have children)</li> + * <li>it makes the handling of text (cdata) blocks more convenient</li> + * <li>it provides convenient methods for getting a mandatory attribute (and throwing an exception + * if it is missing) or an optional attribute (and using a default value if it is missing) + * </ul> + */ +public class SimplePullParser { + public static final String TEXT_TAG = "![CDATA["; + + private final XmlPullParser mParser; + private String mCurrentStartTag; + + /** + * Constructs a new SimplePullParser to parse the xml + * @param parser the underlying parser to use + */ + public SimplePullParser(XmlPullParser parser) { + mParser = parser; + mCurrentStartTag = null; + } + + /** + * Returns the tag of the next element whose depth is parentDepth plus one + * or null if there are no more such elements before the next start tag. When this returns, + * getDepth() and all methods relating to attributes will refer to the element whose tag is + * returned. + * + * @param parentDepth the depth of the parrent of the item to be returned + * @param textBuffer if null then text blocks will be ignored. If + * non-null then text blocks will be added to the builder and TEXT_TAG + * will be returned when one is found + * @return the next of the next child element's tag, TEXT_TAG if a text block is found, or null + * if there are no more child elements or DATA blocks + * @throws IOException propogated from the underlying parser + * @throws ParseException if there was an error parsing the xml. + */ + public String nextTagOrText(int parentDepth, StringBuffer textBuffer) + throws IOException, ParseException { + while (true) { + int eventType = 0; + try { + eventType = mParser.next(); + } catch (XmlPullParserException e) { + throw new ParseException(e); + } + int depth = mParser.getDepth(); + mCurrentStartTag = null; + + if (eventType == XmlPullParser.START_TAG && depth == parentDepth + 1) { + mCurrentStartTag = mParser.getName(); + // TODO: this is an example of how to do logging of the XML +// if (mLogTag != null && Log.isLoggable(mLogTag, Log.DEBUG)) { +// StringBuilder sb = new StringBuilder(); +// for (int i = 0; i < depth; i++) sb.append(" "); +// sb.append("<").append(mParser.getName()); +// int count = mParser.getAttributeCount(); +// for (int i = 0; i < count; i++) { +// sb.append(" "); +// sb.append(mParser.getAttributeName(i)); +// sb.append("=\""); +// sb.append(mParser.getAttributeValue(i)); +// sb.append("\""); +// } +// sb.append(">"); +// Log.d(mLogTag, sb.toString()); +// } + return mParser.getName(); + } + + if (eventType == XmlPullParser.END_TAG && depth == parentDepth) { + // TODO: this is an example of how to do logging of the XML +// if (mLogTag != null && Log.isLoggable(mLogTag, Log.DEBUG)) { +// StringBuilder sb = new StringBuilder(); +// for (int i = 0; i < depth; i++) sb.append(" "); +// sb.append("</>"); // Not quite valid xml but it gets the job done. +// Log.d(mLogTag, sb.toString()); +// } + return null; + } + + if (eventType == XmlPullParser.END_DOCUMENT && parentDepth == 0) { + return null; + } + + if (eventType == XmlPullParser.TEXT && depth == parentDepth) { + if (textBuffer == null) { + continue; + } + String text = mParser.getText(); + textBuffer.append(text); + return TEXT_TAG; + } + } + } + + /** + * The same as nextTagOrText(int, StringBuilder) but ignores text blocks. + */ + public String nextTag(int parentDepth) throws IOException, ParseException { + return nextTagOrText(parentDepth, null /* ignore text */); + } + + /** + * Returns the depth of the current element. The depth is 0 before the first + * element has been returned, 1 after that, etc. + * + * @return the depth of the current element + */ + public int getDepth() { + return mParser.getDepth(); + } + + /** + * Consumes the rest of the children, accumulating any text at this level into the builder. + * + * @param textBuffer + * @throws IOException propogated from the XmlPullParser + * @throws ParseException if there was an error parsing the xml. + */ + public void readRemainingText(int parentDepth, StringBuffer textBuffer) + throws IOException, ParseException { + while (nextTagOrText(parentDepth, textBuffer) != null) { + } + } + + /** + * Returns the number of attributes on the current element. + * + * @return the number of attributes on the current element + */ + public int numAttributes() { + return mParser.getAttributeCount(); + } + + /** + * Returns the name of the nth attribute on the current element. + * + * @return the name of the nth attribute on the current element + */ + public String getAttributeName(int i) { + return mParser.getAttributeName(i); + } + + /** + * Returns the namespace of the nth attribute on the current element. + * + * @return the namespace of the nth attribute on the current element + */ + public String getAttributeNamespace(int i) { + return mParser.getAttributeNamespace(i); + } + + /** + * Returns the string value of the named attribute. + * + * @param namespace the namespace of the attribute + * @param name the name of the attribute + * @param defaultValue the value to return if the attribute is not specified + * @return the value of the attribute + */ + public String getStringAttribute( + String namespace, String name, String defaultValue) { + String value = mParser.getAttributeValue(namespace, name); + if (null == value) return defaultValue; + return value; + } + + /** + * Returns the string value of the named attribute. An exception will + * be thrown if the attribute is not present. + * + * @param namespace the namespace of the attribute + * @param name the name of the attribute @return the value of the attribute + * @throws ParseException thrown if the attribute is missing + */ + public String getStringAttribute(String namespace, String name) throws ParseException { + String value = mParser.getAttributeValue(namespace, name); + if (null == value) { + throw new ParseException( + "missing '" + name + "' attribute on '" + mCurrentStartTag + "' element"); + } + return value; + } + + /** + * Returns the string value of the named attribute. An exception will + * be thrown if the attribute is not a valid integer. + * + * @param namespace the namespace of the attribute + * @param name the name of the attribute + * @param defaultValue the value to return if the attribute is not specified + * @return the value of the attribute + * @throws ParseException thrown if the attribute not a valid integer. + */ + public int getIntAttribute(String namespace, String name, int defaultValue) + throws ParseException { + String value = mParser.getAttributeValue(namespace, name); + if (null == value) return defaultValue; + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new ParseException("Cannot parse '" + value + "' as an integer"); + } + } + + /** + * Returns the string value of the named attribute. An exception will + * be thrown if the attribute is not present or is not a valid integer. + * + * @param namespace the namespace of the attribute + * @param name the name of the attribute @return the value of the attribute + * @throws ParseException thrown if the attribute is missing or not a valid integer. + */ + public int getIntAttribute(String namespace, String name) + throws ParseException { + String value = getStringAttribute(namespace, name); + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new ParseException("Cannot parse '" + value + "' as an integer"); + } + } + + /** + * Returns the string value of the named attribute. An exception will + * be thrown if the attribute is not a valid long. + * + * @param namespace the namespace of the attribute + * @param name the name of the attribute @return the value of the attribute + * @throws ParseException thrown if the attribute is not a valid long. + */ + public long getLongAttribute(String namespace, String name, long defaultValue) + throws ParseException { + String value = mParser.getAttributeValue(namespace, name); + if (null == value) return defaultValue; + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + throw new ParseException("Cannot parse '" + value + "' as a long"); + } + } + + /** + * Returns the string value of the named attribute. An exception will + * be thrown if the attribute is not present or is not a valid long. + * + * @param namespace the namespace of the attribute + * @param name the name of the attribute @return the value of the attribute + * @throws ParseException thrown if the attribute is missing or not a valid long. + */ + public long getLongAttribute(String namespace, String name) + throws ParseException { + String value = getStringAttribute(namespace, name); + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + throw new ParseException("Cannot parse '" + value + "' as a long"); + } + } + + public static final class ParseException extends Exception { + public ParseException(String message) { + super(message); + } + + public ParseException(String message, Throwable cause) { + // constructor Exception(String, Throwable) is not supported in J2ME + super(message + ", Cause: " + String.valueOf(cause)); + } + + public ParseException(Throwable cause) { + // constructor Exception(Throwable) is not supported in J2ME + super("Cause: " + String.valueOf(cause)); + } + } +} diff --git a/src/com/google/wireless/gdata2/parser/xml/XmlGDataParser.java b/src/com/google/wireless/gdata2/parser/xml/XmlGDataParser.java new file mode 100644 index 0000000..0ea6d81 --- /dev/null +++ b/src/com/google/wireless/gdata2/parser/xml/XmlGDataParser.java @@ -0,0 +1,770 @@ +// Copyright 2008 The Android Open Source Project + +package com.google.wireless.gdata2.parser.xml; + +import com.google.wireless.gdata2.data.Entry; +import com.google.wireless.gdata2.data.Feed; +import com.google.wireless.gdata2.data.StringUtils; +import com.google.wireless.gdata2.data.XmlUtils; +import com.google.wireless.gdata2.parser.GDataParser; +import com.google.wireless.gdata2.parser.ParseException; +import com.google.wireless.gdata2.data.batch.BatchInterrupted; +import com.google.wireless.gdata2.data.batch.BatchStatus; +import com.google.wireless.gdata2.data.batch.BatchUtils; + +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"; + + /** The openSearch namespace Uri */ + public static final String NAMESPACE_OPENSEARCH_URI = + "http://a9.com/-/spec/opensearch/1.1/"; + + /** 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"; + + /** Namespace prefix for GData batch operations */ + public static final String NAMESPACE_BATCH = "batch"; + + /** Namespace uri for GData batch operations */ + public static final String NAMESPACE_BATCH_URI = + "http://schemas.google.com/gdata/batch"; + + private final InputStream is; + private final XmlPullParser parser; + private boolean isInBadState; + private String fields; + + /** + * Creates a new XmlGDataParser for a feed in the provided InputStream. + * @param is The InputStream that should be parsed. + * @param parser The xmlpullparser to be used + * @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; + + if (!parser.getFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES)) + { + throw new IllegalStateException("A XmlGDataParser needs to be " + + "constructed with a namespace aware XmlPullParser"); + } + + 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.gdata2.parser.GDataParser#parseFeedEnvelope() + */ + public final Feed parseFeedEnvelope() throws ParseException { + int eventType; + fields = null; + 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 (XmlNametable.PARTIAL.equals(name)) { + try { + return parsePartialFeed(); + } catch (XmlPullParserException xppe) { + throw new ParseException("Unable to parse <partial> feed start", xppe); + } catch (IOException ioe) { + throw new ParseException("Unable to parse <partial> feed start", ioe); + } + } else if (XmlNametable.FEED.equals(name)) { + try { + return parseFeed(); + } catch (XmlPullParserException xppe) { + throw new ParseException("Unable to parse <feed>.", + xppe); + } catch (IOException ioe) { + throw new ParseException("Unable to parse <feed>.", + ioe); + } + } + break; + default: + // ignore + break; + } + + try { + eventType = parser.next(); + } catch (XmlPullParserException xppe) { + throw new ParseException("Could not read next event.", xppe); + } catch (IOException ioe) { + throw new ParseException("Could not read next event." , ioe); + } + } + throw new ParseException("No <feed> found in document."); + } + + /** + * @return 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 partial feed (but not any entries). This requires a + * namespace enabled parser + * + * @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 parsePartialFeed() throws XmlPullParserException, IOException { + // first thing to do is get the attribute we care about from the partial element + fields = parser.getAttributeValue(null /* ns */, XmlNametable.FIELDS); + + int eventType = parser.next(); + while (eventType != XmlPullParser.END_DOCUMENT) { + switch (eventType) { + case XmlPullParser.START_TAG: + String name = parser.getName(); + String namespace = parser.getNamespace(); + + if (XmlGDataParser.NAMESPACE_ATOM_URI.equals(namespace)) { + if (XmlNametable.FEED.equals(name)) { + return parseFeed(); + } + } + default: + break; + } + eventType = parser.next(); + } + // if we get here, we have no feed + return null; + } + + + /** + * Parses the feed (but not any entries). This requires a + * namespace enabled parser + * + * @return A new {@link Feed} containing information about the feed. + * @throws XmlPullParserException Thrown if the XML document cannot be + * parsed. + * @throws IOException Thrown if the {@link InputStream} behind the feed + * cannot be read. + */ + private final Feed parseFeed() throws XmlPullParserException, IOException { + Feed feed = createFeed(); + // parsing <feed> + feed.setETag(parser.getAttributeValue(NAMESPACE_GD_URI, XmlNametable.ETAG)); + + int eventType = parser.next(); + while (eventType != XmlPullParser.END_DOCUMENT) { + switch (eventType) { + case XmlPullParser.START_TAG: + String name = parser.getName(); + String namespace = parser.getNamespace(); + + if (XmlGDataParser.NAMESPACE_OPENSEARCH_URI.equals(namespace)) { + if (XmlNametable.TOTAL_RESULTS.equals(name)) { + feed.setTotalResults(StringUtils.parseInt( + XmlUtils.extractChildText(parser), 0)); + } else if (XmlNametable.START_INDEX.equals(name)) { + feed.setStartIndex(StringUtils.parseInt( + XmlUtils.extractChildText(parser), 0)); + } else if (XmlNametable.ITEMS_PER_PAGE.equals(name)) { + feed.setItemsPerPage(StringUtils.parseInt( + XmlUtils.extractChildText(parser), 0)); + } + } else if (XmlGDataParser.NAMESPACE_ATOM_URI.equals(namespace)) { + if (XmlNametable.TITLE.equals(name)) { + feed.setTitle(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.ID.equals(name)) { + feed.setId(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.UPDATED.equals(name)) { + feed.setLastUpdated(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.CATEGORY.equals(name)) { + String category = + parser.getAttributeValue(null /* ns */, XmlNametable.TERM); + if (!StringUtils.isEmpty(category)) { + feed.setCategory(category); + } + String categoryScheme = + parser.getAttributeValue(null /* ns */, XmlNametable.SCHEME); + if (!StringUtils.isEmpty(categoryScheme)) { + feed.setCategoryScheme(categoryScheme); + } + } else if (XmlNametable.ENTRY.equals(name)) { + // stop parsing here. + return feed; + } + } else { + handleExtraElementInFeed(feed); + } + 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. + * @throws XmlPullParserException + * @throws IOException + */ + protected void handleExtraElementInFeed(Feed feed) + throws XmlPullParserException, IOException { + // no-op in this class. + } + + /* + * (non-Javadoc) + * @see com.google.wireless.gdata2.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.gdata2.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, in the future, we have a batch feed with partial results, the next element + // can be either an entry or a partial element + + if ((!XmlNametable.ENTRY.equals(name) && + !XmlNametable.PARTIAL.equals(name))) { + throw new ParseException("Expected <entry> or <partial>: Actual element: " + + "<" + name + ">"); + } + + if (entry == null) { + entry = createEntry(); + } else { + entry.clear(); + } + + try { + if (XmlNametable.ENTRY.equals(name)) { + handleEntry(entry); + } else { + handlePartialEntry(entry); + } + } catch (ParseException xppe1) { + try { + if (hasMoreData()) skipToNextEntry(); + } catch (XmlPullParserException xppe2) { + // squelch the error -- let the original one stand. + // set isInBadState to ensure that the next call to hasMoreData() will return false. + isInBadState = true; + } + throw new ParseException("Could not parse <entry>, " + entry, xppe1); + } catch (XmlPullParserException xppe1) { + try { + if (hasMoreData()) skipToNextEntry(); + } catch (XmlPullParserException xppe2) { + // squelch the error -- let the original one stand. + // set isInBadState to ensure that the next call to hasMoreData() will return false. + isInBadState = true; + } + throw new ParseException("Could not parse <entry>, " + entry, xppe1); + } + return entry; + } + + /** + * Parses a GData entry. You can either call {@link #parseFeedEnvelope()} 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 { + fields = null; + 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 (XmlNametable.PARTIAL.equals(name)) { + try { + handlePartialEntry(entry); + return entry; + } catch (XmlPullParserException xppe) { + throw new ParseException("Unable to parse <partial> entry.", + xppe); + } catch (IOException ioe) { + throw new ParseException("Unable to parse <partial> entry.", + ioe); + } + } else if (XmlNametable.ENTRY.equals(name)) { + try { + handleEntry(entry); + return entry; + } catch (XmlPullParserException xppe) { + throw new ParseException("Unable to parse <entry>.", + xppe); + } catch (IOException ioe) { + throw new ParseException("Unable to parse <entry>.", + ioe); + } + } + break; + default: + // ignore + break; + } + + try { + eventType = parser.next(); + } catch (XmlPullParserException xppe) { + throw new ParseException("Could not read next event.", xppe); + } + } + throw new ParseException("No <entry> found in document."); + } + + /** + * Skips the rest of the current entry until the parser reaches the next entry, if any. + * Does nothing if the parser is already at the beginning of an entry. + * @throws IOException + * @throws XmlPullParserException + */ + protected void skipToNextEntry() throws IOException, XmlPullParserException { + if (!hasMoreData()) { + throw new IllegalStateException("you shouldn't call this if hasMoreData() is false"); + } + + int eventType = parser.getEventType(); + + // skip ahead until we reach an <entry> tag. + while (eventType != XmlPullParser.END_DOCUMENT) { + switch (eventType) { + case XmlPullParser.START_TAG: + if (XmlNametable.ENTRY.equals(parser.getName())) { + return; + } + break; + } + eventType = parser.next(); + } + } + + /** + * Supply a 'skipSubTree' API which, for some reason, the kxml2 pull parser + * hasn't implemented. + * @throws IOException + * @throws XmlPullParserException + */ + protected void skipSubTree() + throws XmlPullParserException, IOException { + // Iterate the remaining structure for this element, discarding events + // until we hit the element's corresponding end tag. + int level = 1; + while (level > 0) { + int eventType = parser.next(); + switch (eventType) { + case XmlPullParser.START_TAG: + ++level; + break; + case XmlPullParser.END_TAG: + --level; + break; + default: + break; + } + } + } + + /** + * Parses the current partial start in the XML document. Assumes + * that the parser is currently pointing just at the beginning + * of an <partial>. + * + * @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. + * @throws ParseException Thrown in the stream can not be parsed into gdata + */ + protected void handlePartialEntry(Entry entry) + throws XmlPullParserException, IOException, ParseException { + // first thing we do is to get the attributes out of the parser for this entry + // so we verify that we are at the start of an entry + if (!XmlNametable.PARTIAL.equals(parser.getName())) { + throw new + IllegalStateException("Expected <partial>: Actual element: <" + + parser.getName() + ">"); + } + + fields = parser.getAttributeValue(null /* ns */, XmlNametable.FIELDS); + // now skip to the next parser event + parser.next(); + + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + switch (eventType) { + case XmlPullParser.START_TAG: + String name = parser.getName(); + if (XmlNametable.ENTRY.equals(name)) { + handleEntry(entry); + return; + } + default: + break; + } + eventType = parser.next(); + } + } + + /** + * Parses the current entry in the XML document. Assumes that the parser + * is currently pointing just at the beginning of 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. + * @throws ParseException Thrown in the stream can not be parsed into gdata + */ + protected void handleEntry(Entry entry) + throws XmlPullParserException, IOException, ParseException { + // first thing we do is to get the attributes out of the parser for this entry + // so we verify that we are at the start of an entry + if (!XmlNametable.ENTRY.equals(parser.getName())) { + throw new + IllegalStateException("Expected <entry>: Actual element: <" + + parser.getName() + ">"); + } + + entry.setETag(parser.getAttributeValue(NAMESPACE_GD_URI, XmlNametable.ETAG)); + entry.setFields(fields); + // now skip to the next parser event + parser.next(); + + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + switch (eventType) { + case XmlPullParser.START_TAG: + String name = parser.getName(); + if (XmlNametable.ENTRY.equals(name)) { + // stop parsing here. + return; + } + // for each start tag, we call our subclasses first. Only do the default + // processing, if they have not done it. + if (handleDefaultEntryElements(entry)){ + break; + } + + if (NAMESPACE_BATCH_URI.equals(parser.getNamespace())) { + // We must check for the BATCH namespace first in case the tag name + // is reused in another namespace. e.g. "id". + handleBatchInfo(entry); + } else if (XmlNametable.ID.equals(name)) { + entry.setId(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.TITLE.equals(name)) { + entry.setTitle(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.LINK.equals(name)) { + String rel = + parser.getAttributeValue(null /* ns */, XmlNametable.REL); + String type = + parser.getAttributeValue(null /* ns */, XmlNametable.TYPE); + String href = + parser.getAttributeValue(null /* ns */, XmlNametable.HREF); + if (XmlNametable.EDIT_REL.equals(rel)) { + entry.setEditUri(href); + } else if (XmlNametable.ALTERNATE_REL.equals(rel) + && XmlNametable.TEXTHTML.equals(type)) { + entry.setHtmlUri(href); + } else { + handleExtraLinkInEntry(rel, + type, + href, + entry); + } + } else if (XmlNametable.SUMMARY.equals(name)) { + entry.setSummary(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.CONTENT.equals(name)) { + entry.setContentType(parser.getAttributeValue(null /* ns */, XmlNametable.TYPE)); + entry.setContentSource(parser.getAttributeValue(null /* ns */, XmlNametable.SRC)); + entry.setContent(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.AUTHOR.equals(name)) { + handleAuthor(entry); + } else if (XmlNametable.CATEGORY.equals(name)) { + String category = + parser.getAttributeValue(null /* ns */, XmlNametable.TERM); + if (category != null && category.length() > 0) { + entry.setCategory(category); + } + String categoryScheme = + parser.getAttributeValue(null /* ns */, XmlNametable.SCHEME); + if (categoryScheme != null && category.length() > 0) { + entry.setCategoryScheme(categoryScheme); + } + } else if (XmlNametable.PUBLISHED.equals(name)) { + entry.setPublicationDate( + XmlUtils.extractChildText(parser)); + } else if (XmlNametable.UPDATED.equals(name)) { + entry.setUpdateDate(XmlUtils.extractChildText(parser)); + } else if (XmlNametable.DELETED.equals(name)) { + entry.setDeleted(true); + } else { + handleExtraElementInEntry(entry); + } + break; + default: + break; + } + + eventType = parser.next(); + } + entry.validate(); + } + + private void handleAuthor(Entry entry) + throws XmlPullParserException, IOException { + + int eventType = parser.getEventType(); + String name = parser.getName(); + + if (eventType != XmlPullParser.START_TAG || + (!XmlNametable.AUTHOR.equals(parser.getName()))) { + // should not happen. + throw new + IllegalStateException("Expected <author>: Actual element: <" + + parser.getName() + ">"); + } + + eventType = parser.next(); + while (eventType != XmlPullParser.END_DOCUMENT) { + switch (eventType) { + case XmlPullParser.START_TAG: + name = parser.getName(); + if (XmlNametable.NAME.equals(name)) { + String authorName = XmlUtils.extractChildText(parser); + entry.setAuthor(authorName); + } else if (XmlNametable.EMAIL.equals(name)) { + String email = XmlUtils.extractChildText(parser); + entry.setEmail(email); + } + break; + case XmlPullParser.END_TAG: + name = parser.getName(); + if (XmlNametable.AUTHOR.equals(name)) { + return; + } + default: + // ignore + } + + eventType = parser.next(); + } + } + + private void handleBatchInfo(Entry entry) + throws IOException, XmlPullParserException { + String name = parser.getName(); + if (XmlNametable.STATUS.equals(name)) { + BatchStatus status = new BatchStatus(); + BatchUtils.setBatchStatus(entry, status); + status.setStatusCode(getIntAttribute(parser, XmlNametable.CODE)); + status.setReason(getAttribute(parser, XmlNametable.REASON)); + status.setContentType(getAttribute(parser, XmlNametable.CONTENT_TYPE)); + // TODO: Read sub-tree into content. + skipSubTree(); + } else if (XmlNametable.ID.equals(name)) { + BatchUtils.setBatchId(entry, XmlUtils.extractChildText(parser)); + } else if (XmlNametable.OPERATION.equals(name)) { + BatchUtils.setBatchOperation(entry, getAttribute(parser, XmlNametable.TYPE)); + } else if ("interrupted".equals(name)) { + BatchInterrupted interrupted = new BatchInterrupted(); + BatchUtils.setBatchInterrupted(entry, interrupted); + interrupted.setReason(getAttribute(parser, XmlNametable.REASON)); + interrupted.setErrorCount(getIntAttribute(parser, XmlNametable.ERROR)); + interrupted.setSuccessCount(getIntAttribute(parser, XmlNametable.SUCCESS)); + interrupted.setTotalCount(getIntAttribute(parser, XmlNametable.PARSED)); + // TODO: Read sub-tree into content. + skipSubTree(); + } else { + throw new XmlPullParserException("Unexpected batch element " + name); + } + } + + private static String getAttribute(XmlPullParser parser, String name) { + return parser.getAttributeValue(null /* ns */, name); + } + + private static int getIntAttribute(XmlPullParser parser, String name) { + return Integer.parseInt(getAttribute(parser, name)); + } + + /* + * (non-Javadoc) + * @see com.google.wireless.gdata2.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. + * @throws IOException + * @throws XmlPullParserException + * @throws ParseException Thrown in the stream can not be parsed into gdata + */ + protected void handleExtraElementInEntry(Entry entry) + throws XmlPullParserException, IOException, ParseException { + // no-op in this class. + } + + /** + * Hook that allows a subclass to override default parsing + * behaviour. If the subclass returns true from this call, + * no default parsing will happen for the currently parsed tag + * @param entry The {@link Entry} being filled. + * @return true if the subclass handled the parsing. + * @throws IOException + * @throws XmlPullParserException + */ + protected boolean handleDefaultEntryElements(Entry entry) + throws XmlPullParserException, IOException { + // no-op in this class. + return false; + } + /** + * 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. + * @throws IOException + * @throws XmlPullParserException + */ + 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/gdata2/parser/xml/XmlMediaEntryGDataParser.java b/src/com/google/wireless/gdata2/parser/xml/XmlMediaEntryGDataParser.java new file mode 100644 index 0000000..d15f906 --- /dev/null +++ b/src/com/google/wireless/gdata2/parser/xml/XmlMediaEntryGDataParser.java @@ -0,0 +1,44 @@ +// Copyright 2009 The Android Open Source Project + +package com.google.wireless.gdata2.parser.xml; + +import com.google.wireless.gdata2.data.MediaEntry; +import com.google.wireless.gdata2.data.Entry; +import com.google.wireless.gdata2.data.Feed; +import com.google.wireless.gdata2.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.gdata2.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.gdata2.parser.xml.XmlGDataParser#createFeed() + */ + protected Feed createFeed() { + throw new IllegalStateException("there is no such thing as a feed of media entries"); + } + + /* + * (non-Javadoc) + * @see com.google.wireless.gdata2.parser.xml.XmlGDataParser#createEntry() + */ + protected Entry createEntry() { + return new MediaEntry(); + } +} diff --git a/src/com/google/wireless/gdata2/parser/xml/XmlNametable.java b/src/com/google/wireless/gdata2/parser/xml/XmlNametable.java new file mode 100644 index 0000000..381a48c --- /dev/null +++ b/src/com/google/wireless/gdata2/parser/xml/XmlNametable.java @@ -0,0 +1,53 @@ +// Copyright 2009 The Android Open Source Project + +package com.google.wireless.gdata2.parser.xml; + +/** + * Class to hold static strings that are used to parse and + * serialize the xml elements + */ +public final class XmlNametable { + private XmlNametable() {} + + // generic strings for attributes + public static String PARTIAL = "partial"; + public static String FIELDS = "fields"; + public static String ENTRY = "entry"; + public static String FEED = "feed"; + public static String UTF8 = "UTF-8"; + public static String EDIT_REL = "edit"; + public static String ALTERNATE_REL = "alternate"; + public static String REL = "rel"; + public static String LINK = "link"; + public static String HREF = "href"; + public static String ETAG = "etag"; + public static String TYPE = "type"; + public static String SRC = "src"; + public static String TEXT = "text"; + public static String TEXTHTML = "text/html"; + public static String ID = "id"; + public static String TITLE = "title"; + public static String SUMMARY = "summary"; + public static String CONTENT = "content"; + public static String AUTHOR = "author"; + public static String EMAIL = "email"; + public static String NAME = "name"; + public static String CATEGORY = "category"; + public static String TERM = "term"; + public static String SCHEME = "scheme"; + public static String PUBLISHED = "published"; + public static String UPDATED = "updated"; + public static String OPERATION = "operation"; + public static String TOTAL_RESULTS = "totalResults"; + public static String START_INDEX = "startIndex"; + public static String ITEMS_PER_PAGE = "itemsPerPage"; + public static String DELETED = "deleted"; + public static String STATUS = "status"; + public static String CODE = "code"; + public static String REASON = "reason"; + public static String CONTENT_TYPE = "content-type"; + public static String ERROR = "error"; + public static String SUCCESS = "success"; + public static String PARSED = "parsed"; + +} diff --git a/src/com/google/wireless/gdata2/parser/xml/XmlParserFactory.java b/src/com/google/wireless/gdata2/parser/xml/XmlParserFactory.java new file mode 100644 index 0000000..650db50 --- /dev/null +++ b/src/com/google/wireless/gdata2/parser/xml/XmlParserFactory.java @@ -0,0 +1,31 @@ +// Copyright 2007 The Android Open Source Project + +package com.google.wireless.gdata2.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/gdata2/parser/xml/package.html b/src/com/google/wireless/gdata2/parser/xml/package.html new file mode 100644 index 0000000..1c9bf9d --- /dev/null +++ b/src/com/google/wireless/gdata2/parser/xml/package.html @@ -0,0 +1,5 @@ +<html> +<body> + {@hide} +</body> +</html> diff --git a/src/com/google/wireless/gdata2/serializer/GDataSerializer.java b/src/com/google/wireless/gdata2/serializer/GDataSerializer.java new file mode 100644 index 0000000..587790d --- /dev/null +++ b/src/com/google/wireless/gdata2/serializer/GDataSerializer.java @@ -0,0 +1,69 @@ +// Copyright 2007 The Android Open Source Project + +package com.google.wireless.gdata2.serializer; + +import com.google.wireless.gdata2.parser.ParseException; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Interface for serializing GData entries. A serializer has to be aware + * what mode it is in while it is serializing. + */ +public interface GDataSerializer { + + /** + * 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; + + /** + * Serialize the entry as part of a batch of operations. + */ + public static final int FORMAT_BATCH = 3; + + /** + * Returns the Content-Type for this serialization format. + * @return The Content-Type for this serialization format. + */ + String getContentType(); + + /** + * Returns if this serializer supports a partial representation + * of the underlying content + * + * @return boolean True if a partial representation will be + * created + */ + boolean getSupportsPartial(); + + /** + * Serializes a GData entry to the provided {@link OutputStream}, using the + * specified serialization format. + * + * @see #FORMAT_FULL + * @see #FORMAT_CREATE + * @see #FORMAT_UPDATE + * @see #FORMAT_BATCH + * + * @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/gdata2/serializer/package.html b/src/com/google/wireless/gdata2/serializer/package.html new file mode 100644 index 0000000..1c9bf9d --- /dev/null +++ b/src/com/google/wireless/gdata2/serializer/package.html @@ -0,0 +1,5 @@ +<html> +<body> + {@hide} +</body> +</html> diff --git a/src/com/google/wireless/gdata2/serializer/xml/XmlBatchGDataSerializer.java b/src/com/google/wireless/gdata2/serializer/xml/XmlBatchGDataSerializer.java new file mode 100644 index 0000000..22a6824 --- /dev/null +++ b/src/com/google/wireless/gdata2/serializer/xml/XmlBatchGDataSerializer.java @@ -0,0 +1,108 @@ +// Copyright 2008 Google Inc. All Rights Reserved. + +package com.google.wireless.gdata2.serializer.xml; + +import com.google.wireless.gdata2.serializer.GDataSerializer; +import com.google.wireless.gdata2.parser.ParseException; +import com.google.wireless.gdata2.parser.xml.XmlParserFactory; +import com.google.wireless.gdata2.parser.xml.XmlGDataParser; +import com.google.wireless.gdata2.parser.xml.XmlNametable; +import com.google.wireless.gdata2.client.GDataParserFactory; +import com.google.wireless.gdata2.data.Entry; + +import org.xmlpull.v1.XmlSerializer; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.OutputStream; +import java.io.IOException; +import java.util.Enumeration; + +/** + * Serializes a batch of GData requests as an XML stream. + */ +public class XmlBatchGDataSerializer implements GDataSerializer { + + private final GDataParserFactory gdataFactory; + private final XmlParserFactory xmlFactory; + private final Enumeration batch; + + /* + * Constructs an XmlBatchGDataSerializer for serializing the given batch + * of GData requests + * + * @param gdataFactory used to create serializers for individual requests + * @param xmlFactory used to create the XML stream + * @param Enumeration the batch of requests to serialize + */ + public XmlBatchGDataSerializer(GDataParserFactory gdataFactory, + XmlParserFactory xmlFactory, Enumeration batch) { + this.gdataFactory = gdataFactory; + this.xmlFactory = xmlFactory; + this.batch = batch; + } + + /* (non-Javadoc) + * @see GDataSerializer#getContentType() + */ + public String getContentType() { + return "application/atom+xml"; + } + + /* (non-Javadoc) + * @see GDataSerializer#doesSupportPartial() + */ + public boolean getSupportsPartial() { + return false; + } + + + /* (non-Javadoc) + * @see GDataSerializer#serialize() + */ + public void serialize(OutputStream out, int format) + throws IOException, ParseException { + XmlSerializer serializer; + try { + serializer = xmlFactory.createSerializer(); + } catch (XmlPullParserException e) { + throw new ParseException("Unable to create XmlSerializer.", e); + } + + serializer.setOutput(out, XmlNametable.UTF8); + serializer.startDocument(XmlNametable.UTF8, Boolean.FALSE); + + declareNamespaces(serializer); + + boolean first = true; + while (batch.hasMoreElements()) { + Entry entry = (Entry) batch.nextElement(); + XmlEntryGDataSerializer entrySerializer = (XmlEntryGDataSerializer) + gdataFactory.createSerializer(entry); + + if (first) { + // Let the first entry serializer declare extra namespaces. + first = false; + serializer.startTag(XmlGDataParser.NAMESPACE_ATOM_URI, XmlNametable.FEED); + entrySerializer.declareExtraEntryNamespaces(serializer); + } + entrySerializer.serialize(out, GDataSerializer.FORMAT_BATCH); + } + + if (first) { + serializer.startTag(XmlGDataParser.NAMESPACE_ATOM_URI, XmlNametable.FEED); + } + + serializer.endTag(XmlGDataParser.NAMESPACE_ATOM_URI, XmlNametable.FEED); + serializer.endDocument(); + serializer.flush(); + } + + private static void declareNamespaces(XmlSerializer serializer) throws IOException { + serializer.setPrefix("" /* default ns */, + XmlGDataParser.NAMESPACE_ATOM_URI); + serializer.setPrefix(XmlGDataParser.NAMESPACE_GD, + XmlGDataParser.NAMESPACE_GD_URI); + serializer.setPrefix(XmlGDataParser.NAMESPACE_BATCH, + XmlGDataParser.NAMESPACE_BATCH_URI); + } +} diff --git a/src/com/google/wireless/gdata2/serializer/xml/XmlEntryGDataSerializer.java b/src/com/google/wireless/gdata2/serializer/xml/XmlEntryGDataSerializer.java new file mode 100644 index 0000000..f9fad40 --- /dev/null +++ b/src/com/google/wireless/gdata2/serializer/xml/XmlEntryGDataSerializer.java @@ -0,0 +1,326 @@ +// Copyright 2007 The Android Open Source Project + +package com.google.wireless.gdata2.serializer.xml; + +import com.google.wireless.gdata2.data.batch.BatchUtils; +import com.google.wireless.gdata2.data.Entry; +import com.google.wireless.gdata2.data.StringUtils; +import com.google.wireless.gdata2.parser.ParseException; +import com.google.wireless.gdata2.parser.xml.XmlGDataParser; +import com.google.wireless.gdata2.parser.xml.XmlNametable; +import com.google.wireless.gdata2.parser.xml.XmlParserFactory; +import com.google.wireless.gdata2.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; + + private final boolean supportsPartial; + + /** + * 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; + supportsPartial = !StringUtils.isEmptyOrWhitespace(this.entry.getFields()); +} + + /** + * 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#getSupportsPartial() + */ + public boolean getSupportsPartial() { + return supportsPartial; + } + + + /* (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, XmlNametable.UTF8); + if (format != FORMAT_BATCH) { + serializer.startDocument(XmlNametable.UTF8, Boolean.FALSE); + declareEntryNamespaces(serializer); + } + final String fields = entry.getFields(); + if (getSupportsPartial()) { + serializer.startTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.PARTIAL); + serializer.attribute(null /* ns */, XmlNametable.FIELDS, fields); + } + + serializer.startTag(XmlGDataParser.NAMESPACE_ATOM_URI, XmlNametable.ENTRY); + + serializeEntryContents(serializer, format); + + serializer.endTag(XmlGDataParser.NAMESPACE_ATOM_URI, XmlNametable.ENTRY); + + if (getSupportsPartial()) { + serializer.endTag(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.PARTIAL); + } + + if (format != FORMAT_BATCH) { + serializer.endDocument(); + } + serializer.flush(); + } + + private 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 void serializeEntryContents(XmlSerializer serializer, int format) + throws ParseException, IOException { + + if (format == FORMAT_BATCH) { + serializeBatchInfo(serializer); + } + + if (format != FORMAT_CREATE) { + serializeId(serializer, entry.getId()); + } + + serializeTitle(serializer, entry.getTitle()); + + if (format != FORMAT_CREATE) { + serializeLink(serializer, XmlNametable.EDIT_REL /* rel */, + entry.getEditUri(), null /* type */, null /* etag */ ); + serializeLink(serializer, XmlNametable.ALTERNATE_REL /* rel */, + entry.getHtmlUri(), XmlNametable.TEXTHTML /* type */, null /* etag */ ); + } + + 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 */, XmlNametable.ID); + serializer.text(id); + serializer.endTag(null /* ns */, XmlNametable.ID); + } + + private static void serializeTitle(XmlSerializer serializer, + String title) + throws IOException { + if (StringUtils.isEmpty(title)) { + return; + } + serializer.startTag(null /* ns */, XmlNametable.TITLE); + serializer.text(title); + serializer.endTag(null /* ns */, XmlNametable.TITLE); + } + + /** + * Serializes a link relationship into the xml serializer passed in. + * + * @param serializer The serializer to be used + * @param rel The relationship value (like alternate, edit etc) + * @param href the URI this link points to + * @param type the content type + * @param etag the optional etag if this link specifies a resource + * + * @exception IOException + */ + protected static void serializeLink(XmlSerializer serializer, String rel, String href, + String type, String etag) throws IOException { + if (StringUtils.isEmpty(href)) { + return; + } + serializer.startTag(null /* ns */, XmlNametable.LINK); + serializer.attribute(null /* ns */, XmlNametable.REL, rel); + serializer.attribute(null /* ns */, XmlNametable.HREF, href); + if (!StringUtils.isEmpty(type)) serializer.attribute(null /* ns */, XmlNametable.TYPE, type); + if (!StringUtils.isEmpty(etag)) serializer.attribute(null /* ns */, XmlNametable.ETAG, etag); + serializer.endTag(null /* ns */, XmlNametable.LINK); + } + + private static void serializeSummary(XmlSerializer serializer, + String summary) + throws IOException { + if (StringUtils.isEmpty(summary)) { + return; + } + serializer.startTag(null /* ns */, XmlNametable.SUMMARY); + serializer.text(summary); + serializer.endTag(null /* ns */, XmlNametable.SUMMARY); + } + + private static void serializeContent(XmlSerializer serializer, + String content) + throws IOException { + if (content == null) { + return; + } + serializer.startTag(null /* ns */, XmlNametable.CONTENT); + serializer.attribute(null /* ns */, XmlNametable.TYPE, XmlNametable.TEXT); + serializer.text(content); + serializer.endTag(null /* ns */, XmlNametable.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 */, XmlNametable.AUTHOR); + serializer.startTag(null /* ns */, XmlNametable.NAME); + serializer.text(author); + serializer.endTag(null /* ns */, XmlNametable.NAME); + serializer.startTag(null /* ns */, XmlNametable.EMAIL); + serializer.text(email); + serializer.endTag(null /* ns */, XmlNametable.EMAIL); + serializer.endTag(null /* ns */, XmlNametable.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 */, XmlNametable.CATEGORY); + if (!StringUtils.isEmpty(category)) { + serializer.attribute(null /* ns */, XmlNametable.TERM, category); + } + if (!StringUtils.isEmpty(categoryScheme)) { + serializer.attribute(null /* ns */, XmlNametable.SCHEME, categoryScheme); + } + serializer.endTag(null /* ns */, XmlNametable.CATEGORY); + } + + private static void + serializePublicationDate(XmlSerializer serializer, + String publicationDate) + throws IOException { + if (StringUtils.isEmpty(publicationDate)) { + return; + } + serializer.startTag(null /* ns */, XmlNametable.PUBLISHED); + serializer.text(publicationDate); + serializer.endTag(null /* ns */, XmlNametable.PUBLISHED); + } + + private static void + serializeUpdateDate(XmlSerializer serializer, + String updateDate) + throws IOException { + if (StringUtils.isEmpty(updateDate)) { + return; + } + serializer.startTag(null /* ns */, XmlNametable.UPDATED); + serializer.text(updateDate); + serializer.endTag(null /* ns */, XmlNametable.UPDATED); + } + + private void serializeBatchInfo(XmlSerializer serializer) + throws IOException { + if (!StringUtils.isEmpty(entry.getETag())) { + serializer.attribute(XmlGDataParser.NAMESPACE_GD_URI, XmlNametable.ETAG, + entry.getETag()); + } + if (!StringUtils.isEmpty(BatchUtils.getBatchOperation(entry))) { + serializer.startTag(XmlGDataParser.NAMESPACE_BATCH_URI, XmlNametable.OPERATION); + serializer.attribute(null /* ns */, XmlNametable.TYPE, + BatchUtils.getBatchOperation(entry)); + serializer.endTag(XmlGDataParser.NAMESPACE_BATCH_URI, XmlNametable.OPERATION); + } + if (!StringUtils.isEmpty(BatchUtils.getBatchId(entry))) { + serializer.startTag(XmlGDataParser.NAMESPACE_BATCH_URI, XmlNametable.ID); + serializer.text(BatchUtils.getBatchId(entry)); + serializer.endTag(XmlGDataParser.NAMESPACE_BATCH_URI, XmlNametable.ID); + } + } + +} diff --git a/src/com/google/wireless/gdata2/serializer/xml/package.html b/src/com/google/wireless/gdata2/serializer/xml/package.html new file mode 100644 index 0000000..1c9bf9d --- /dev/null +++ b/src/com/google/wireless/gdata2/serializer/xml/package.html @@ -0,0 +1,5 @@ +<html> +<body> + {@hide} +</body> +</html> |