/*
* Funambol is a mobile platform developed by Funambol, Inc.
* Copyright (C) 2003 - 2007 Funambol, Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by
* the Free Software Foundation with the addition of the following permission
* added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
* WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program; if not, see http://www.gnu.org/licenses or write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA.
*
* You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
* 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Powered by Funambol" logo. If the display of the logo is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Powered by Funambol".
*/
package com.funambol.syncclient.blackberry.parser;
import java.util.Calendar;
import java.util.Date;
import java.util.Hashtable;
import java.util.TimeZone;
import java.util.Vector;
import javax.microedition.pim.Event;//
import javax.microedition.pim.EventList;
import javax.microedition.pim.FieldEmptyException;
import javax.microedition.pim.RepeatRule;
import com.funambol.syncclient.common.StringTools;
import com.funambol.syncclient.util.StaticDataHelper;
import com.funambol.syncclient.spds.SyncException;
import net.rim.blackberry.api.pdap.BlackBerryEvent;//
import net.rim.device.api.ui.component.Dialog;
import net.rim.device.api.i18n.SimpleDateFormat;
/**
* This is not, despite the name, only a parser,
* but rather a formatter with added parsing
* functionalities. For more information about
* the Event fields' values see
* {@link http://www.j2medev.com/api/pim/constant-values.html}
*
*/
public class XMLEventParser extends XMLParser implements EventParser {
private static final String ALLDAY = "AllDayEvent" ;
private static final String NOTE = "Body" ;
private static final String BUSY_STATUS = "BusyStatus" ;//bugfix 869b
private static final String END = "End" ;
private static final String LOCATION = "Location" ;
private static final String SENSITIVITY = "Sensitivity" ;//bugfix 869a
private static final String START = "Start" ;
private static final String SUMMARY = "Subject" ;
private static final String ALARM = "ReminderMinutesBeforeStart" ;
private static final String ALARM_ENABLE = "ReminderSet" ;
private static final String REVISION = "Revision" ;
private static final String DATETIME_FORMAT_UTC
= "yyyyMMdd'T'HHmmss'Z'" ;
private static final String DATETIME_FORMAT_ALLDAY
= "yyyy-MM-dd" ;
private static final String UTC_TIMEZONE = "UTC" ;
private static final String DAY_OF_MONTH = "DayOfMonth" ;//enhancement 868
private static final String DAY_OF_WEEK_MASK = "DayOfWeekMask" ;//enhancement 868
private static final String INSTANCE = "Instance" ;//enhancement 868
private static final String INTERVAL = "Interval" ;//enhancement 868
private static final String IS_RECURRING = "IsRecurring" ;//enhancement 868
private static final String MONTH_OF_YEAR = "MonthOfYear" ;//enhancement 868
private static final String NO_END_DATE = "NoEndDate" ;//enhancement 868
private static final String OCCURRENCES = "Occurrences" ;//enhancement 868
private static final String PATTERN_END_DATE = "PatternEndDate" ;//enhancement 868
private static final String PATTERN_START_DATE
= "PatternStartDate" ;//enhancement 868
private static final String RECURRENCE_TYPE = "RecurrenceType" ;//enhancement 868
//SIF-E
private static final int olSunday = 1;
private static final int olMonday = 2;
private static final int olTuesday = 4;
private static final int olWednesday = 8;
private static final int olThursday = 16;
private static final int olFriday = 32;
private static final int olSaturday = 64;
private String[] tagArray = {
ALLDAY ,
SUMMARY ,
LOCATION ,
NOTE ,
START ,
END ,
REVISION ,
ALARM ,
ALARM_ENABLE ,
IS_RECURRING , //enhancement 868
SENSITIVITY , //bugfix 869a
BUSY_STATUS , //bugfix 869b
DAY_OF_MONTH , //enhancement 868
DAY_OF_WEEK_MASK , //enhancement 868
INSTANCE , //enhancement 868
INTERVAL , //enhancement 868
MONTH_OF_YEAR , //enhancement 868
NO_END_DATE , //enhancement 868
OCCURRENCES , //enhancement 868
PATTERN_END_DATE , //enhancement 868
PATTERN_START_DATE , //enhancement 868
RECURRENCE_TYPE //enhancement 868
};
private EventList list;
private BlackBerryEvent event;
private boolean modify;
/**
* Used in the method
* appendRecurrenceInformation()
* to contain a reference
* to the formatted start date
* sent to the server, to prepare
* the content of the
* {@code <PatternStartDate>}
* element (not supported by
* the RIM's API) in the SIF-E
*/
private String patternStart;
/**
* This attribute stores the data to be cached.
* The data is retrieved by the DataStore after the commit to save
* them in the cache.
*/
private Hashtable cacheData;
/**
* The constructor
*
* @param EventList A BlackBerry event list
* @param Event A BlackBerry event
* @param boolean To modify or not to modify
* @return XMLEventParser
*/
public XMLEventParser(EventList list, Event event, boolean modify) {
this.list = list;
this.event = (BlackBerryEvent)event;
this.modify = modify;
cacheData = null;
START_MARKER = "<appointment>";
END_MARKER = "</appointment>";
}
/**
* This method is invoked to obtain a calendar Event object from
* a calendar event String (in SIF-E format). If the modify flag
* is set to <code>true</code>, it modifies the calendar event.
* If the modify flag is set to {@code false}, it simply adds an
* event to the BlackBerry calendar application
*
* @param eventString A string in SIF-E XML format containing event
* information to be parsed
* @return The {@code Event} that has to be added/modified in
* the BlackBerry calendar application
*/
public Event parseEvent(String eventString) throws SyncException {
// Init the cacheData at each item.
cacheData = new Hashtable();
if (modify) {
return modifyEvent(eventString);
} else {
return addEvent(eventString);
}
}
/**
* This method is invoked to obtain a BlackBerry Event
* object from a contact String coming from the server
* (in SIF-E XML format), when a calendar event has to
* be added to the BlackBerry calendar. This method adds
* a new calendar event coming from the server to the
* data store and returns a reference to that object
*
* @param eventString String containing the calendar event
* information in SIF-E format to be parsed
* @return The BlackBerry {@code Event} object that has to
* be added to the calendar application
*/
private Event addEvent(String eventString) throws SyncException {
boolean allday = false;
Hashtable eventMap = buildMapFromXML(eventString);
if (eventMap != null) {
try {
String field = getValue(eventMap, ALLDAY);
if ("1".equals(field)) {
allday = true;
}
field = getValue(eventMap, SUMMARY);
if (isSupportedField(BlackBerryEvent.SUMMARY, field)) {
event.addString(BlackBerryEvent.SUMMARY, BlackBerryEvent.ATTR_NONE, field);
}
field = getValue(eventMap, LOCATION);
if (isSupportedField(BlackBerryEvent.LOCATION, field)) {
event.addString(BlackBerryEvent.LOCATION, BlackBerryEvent.ATTR_NONE, field);
}
field = getValue(eventMap, NOTE);
if (isSupportedField(BlackBerryEvent.NOTE, field)) {
event.addString(BlackBerryEvent.NOTE, BlackBerryEvent.ATTR_NONE, field);
}
if (isSupportedField(event.ALLDAY, field)) {
if (allday) {
event.addBoolean(event.ALLDAY, event.ATTR_NONE, true);
} else {
event.addBoolean(event.ALLDAY, event.ATTR_NONE, false);
}
}
// RIM API bugs workarounds for dates:
// 1) store in cache all ADE, to check them later during format
// 2) store dates prior to 01/01/1970 (they will be changed into
// 1970 for the device calendar)
// See toString() for the cache restoring.
field = getValue(eventMap, START);
if (field != null && field.length() > 0) {
if (isSupportedField(BlackBerryEvent.START, field)) {
long dateStart = (allday) ? getAlldayDateStart(field)
: getDate(field);
//a date before 1970 is negative
if (allday || dateStart < 0) {
StaticDataHelper.log("[DEBUG]In addEvent() the origianl 'dateStart' to be cached is " + new Date(dateStart) + " [" + dateStart + "]");
cacheData.put(START, new Long(dateStart));
}
if (dateStart < 0) {
/*
* if the year is before 1970, the year
* is set to 1970 for the BlackBerry device
*/
dateStart = adjustDateToEpoch(dateStart);
StaticDataHelper.log("[DEBUG]Adjusted current start date: " + new Date(dateStart));
}
event.addDate(event.START, event.ATTR_NONE, dateStart);
StaticDataHelper.log("[***] Calculated start date: " + dateStart);
StaticDataHelper.log("[***] Re-retrieved from the event: " + event.getDate(event.START, 0));
}
}
field = getValue(eventMap, END);
if (field != null && field.length() > 0) {
if (isSupportedField(BlackBerryEvent.END, field)) {
long dateEnd = (allday) ? getAlldayDateEnd(field)
: getDate(field);
if (allday || dateEnd < 0) {
StaticDataHelper.log("[DEBUG]In addEvent() the origianl 'dateEnd' to be cached is " + new Date(dateEnd) + "[" + dateEnd + "]");
cacheData.put(END, new Long(dateEnd));
}
if(dateEnd < 0) {
dateEnd = adjustDateToEpoch(dateEnd);
StaticDataHelper.log("[DEBUG]Adjusted current end date: " + new Date(dateEnd));
}
event.addDate(event.END, event.ATTR_NONE, dateEnd);
StaticDataHelper.log("[###] Calculated end date: " + dateEnd);
StaticDataHelper.log("[###] Re-retrieved from the event: " + event.getDate(event.END, 0));
}
}
field = getValue(eventMap, REVISION);
if (field != null && field.length() > 0) {
long dateRevision = getDate(field);
if (isSupportedField(BlackBerryEvent.REVISION, field)) {
event.addDate(event.REVISION, event.ATTR_NONE, dateRevision);
}
}
String alarmEnable = getValue(eventMap, ALARM_ENABLE);
if ("1".equals(alarmEnable)) {
field = getValue(eventMap, ALARM);
if (field != null &&
field.length() > 0 &&
isSupportedField(event.ALARM, field)) {
event.addInt(event.ALARM, event.ATTR_NONE, (Integer.parseInt(field) * 60));
}
}
/*
* enhancement 868 (recurrence)
*
* Please pay attention, because
* an old version of the SIF-E
* foresees something like
* <isRecurring>False</isRecurring>
* and 'False' isn't a numeric value
* (e.g. www.scheduleworld.com)
*/
String isRecurring = getValue(eventMap, IS_RECURRING);//e.g. 1
try {
if (isRecurring != "") {
if (Integer.parseInt(isRecurring) == 1) {
parseRecurrence(eventMap);
}
}
} catch (NumberFormatException n) {//legacy SIF-E content ("False" instead of "0")
if (!"false".equalsIgnoreCase(isRecurring)) {
parseRecurrence(eventMap);
}
}
//bugfix 869a
if (list.isSupportedField(BlackBerryEvent.CLASS)) {
field = getValue(eventMap, SENSITIVITY);
// to avoid a NumberFormatException thrown
// by Integer.parseInt() here below when the
// argument is an empty string (e.g. when the
// <Sensitivity> SIF-E tag is empty)
if ("".equals(field)) {
field = "0";
}
int sensitivity = 0;
switch (Integer.parseInt(field)) {
case 0://olNormal
sensitivity = Event.CLASS_PUBLIC;
break;
case 2://olPrivate
sensitivity = Event.CLASS_PRIVATE;
break;
}
event.addInt(BlackBerryEvent.CLASS, Event.ATTR_NONE, sensitivity);
}
//bugfix 869b BusyStatus
if (list.isSupportedField(BlackBerryEvent.FREE_BUSY)) {
/*
* pay attention: the getValue() method
* returns an empty string even though
* the element <BusyStatus> isn't contained
* in the SIF-E message
*/
field = getValue(eventMap, BUSY_STATUS);
/*
* to avoid a NumberFormatException
* parsing an empty string to obtain
* an int in the following switch
*/
if ("".equals(field)) {
/*
* olFree as default when <BusyStatus>
* is empty or even not present in the
* SIF-E message
*/
field = "0";
}
int status = 0;
switch (Integer.parseInt(field)) {
case 0://olFree
status = BlackBerryEvent.FB_FREE;
break;
case 1://olTentative
status = BlackBerryEvent.FB_TENTATIVE;
break;
case 2://olBusy
status = BlackBerryEvent.FB_BUSY;
break;
case 3://olOutOfOffice
status = BlackBerryEvent.FB_OUT_OF_OFFICE;
break;
}
event.addInt(BlackBerryEvent.FREE_BUSY, BlackBerryEvent.ATTR_NONE, status);
}
return event;
}
catch (Throwable e)//Exception
{
StaticDataHelper.log(">>> EXCEPTION in XMLEventParser.addEvent(String) --> " + e.toString());
e.printStackTrace();
return null;
}
}
return null;
}
/**
* This method is invoked to obtain a BlackBerryEvent
* object from a calendar event String coming
* from the server (in SIF-E XML format), when a calendar
* event has to be modified in the BlackBerry calendar
* application. This method adds a modified calendar event
* coming from the server to the data store and returns a
* reference to that object
*
* @param String String containing the event
* information in SIF-E format
* coming from the server to be
* parsed
* @return Event that has to be modified
* and replaced on the device
* @see addEvent()
*/
private Event modifyEvent(String eventString) throws SyncException {
Hashtable eventMap = buildMapFromXML(eventString);
boolean allday = false;
if (eventMap != null) {
try {
String field = getValue(eventMap, ALLDAY);
if ("1".equals(field)) {
allday = true;
}
field = getValue(eventMap, SUMMARY);
if (isSupportedField(event.SUMMARY, field)) {
if (event.countValues(event.SUMMARY) > 0) {
event.setString(event.SUMMARY, 0, event.ATTR_NONE, field);
}
else {
// Isn't it wrong to remove it if count is 0 ?
//event.removeValue(BlackBerryEvent.SUMMARY, 0);
event.addString(BlackBerryEvent.SUMMARY, Event.ATTR_NONE, field);
}
}
field = getValue(eventMap, LOCATION);
if (isSupportedField(event.LOCATION, field)) {
if (event.countValues(event.LOCATION) > 0) {
event.setString(event.LOCATION, 0, event.ATTR_NONE, field);
} else {
event.addString(BlackBerryEvent.LOCATION, Event.ATTR_NONE, field);
}
}
field = getValue(eventMap, NOTE);
if (isSupportedField(event.NOTE, field) && event.countValues(event.NOTE) > 0) {
event.setString(BlackBerryEvent.NOTE, 0, Event.ATTR_NONE, field);
} else {
event.addString(BlackBerryEvent.NOTE, Event.ATTR_NONE, field);
}
field = getValue(eventMap, START);
if (field != null && field.length() > 0) {
long dateStart = (allday) ? getAlldayDateStart(field)
: getDate(field);
if (allday) {
cacheData.put(START, new Long(dateStart));
}
try {
event.removeValue(BlackBerryEvent.START, 0);
event.addDate(BlackBerryEvent.START, Event.ATTR_NONE, dateStart);
}
catch (Exception e){
if (!allday){
cacheData.put(START, new Long(dateStart));
}
dateStart = adjustDateToEpoch(dateStart);
}
}
field = getValue(eventMap, END);
if (field != null && field.length() > 0) {
long dateEnd = (allday) ? getAlldayDateEnd(field)
: getDate(field);
if (allday) {
cacheData.put(END, new Long(dateEnd));
}
try {
//event.removeValue(event.END, 0);
//event.addDate(event.END, event.ATTR_NONE, dateEnd);
event.setDate(event.END, 0, event.ATTR_NONE, dateEnd);
}
catch (Exception e) {
if (!allday){
cacheData.put(END, new Long(dateEnd));
}
dateEnd = adjustDateToEpoch(dateEnd);
}
}
if (isSupportedField(event.ALLDAY, field)) {
// XXX: is setBoolean broken for this?
if (event.countValues(event.ALLDAY) > 0) {
event.removeValue(event.ALLDAY, 0);
}
if (allday) {
event.addBoolean(event.ALLDAY, event.ATTR_NONE, true);
}
else {
event.addBoolean(event.ALLDAY, event.ATTR_NONE, false);
}
}
field = getValue(eventMap, REVISION);
if (field != null && field.length() > 0) {
long dateRevision = getDate(field);
if (isSupportedAttributedField(event.END, event.ATTR_NONE, field)) {
event.removeValue(event.REVISION, 0);
event.addDate(event.REVISION, event.ATTR_NONE, dateRevision);
}
}
String alarmEnable = getValue(eventMap, ALARM_ENABLE);
if ("1".equals(alarmEnable)) {
field = getValue(eventMap, ALARM);
if (field != null && field.length() > 0) {
if (isSupportedField(event.ALARM, field)){
int alarmSecondsBefore = getAlarmSecondsBefore(field) * 60 ;
if(event.countValues(event.ALARM) > 0) {
event.setInt(event.ALARM, 0, event.ATTR_NONE, alarmSecondsBefore);
}
else {
event.addInt(event.ALARM, event.ATTR_NONE, alarmSecondsBefore);
}
}
}
}
else {
field = getValue(eventMap, ALARM);
if (field != null && field.length() > 0) {
if (isSupportedAttributedField(event.ALARM, event.ATTR_NONE, field)
&& event.countValues(event.ALARM) > 0) {
event.removeValue(event.ALARM, 0);
}
}
}
/*
* enhancement 868 (recurrence)
*
* Please pay attention, because
* an old version of the SIF-E
* foresees something like
* <isRecurring>False</isRecurring>
* and 'False' isn't a numeric value
* (e.g. www.scheduleworld.com)
*/
String isRecurring = getValue(eventMap, IS_RECURRING);//1
try {
if (Integer.parseInt(isRecurring) == 1) {
parseRecurrence(eventMap);
}
} catch (NumberFormatException n) {//legacy SIF-E content ("False" instead of "0")
if (!"false".equalsIgnoreCase(isRecurring)) {
parseRecurrence(eventMap);
}
}
/*
* bugfix 869a
*
* In older versions this code block was never entered,
* because the device implementing the RIM Java API
* (both real and simulated) didn't support the
* sensitivity field
*/
if (list.isSupportedField(BlackBerryEvent.CLASS)) {
field = getValue(eventMap, SENSITIVITY);
// to avoid a NumberFormatException thrown
// by Integer.parseInt() here below when the
// argument is an empty string (e.g. when the
// <Sensitivity> SIF-E tag is empty)
if ("".equals(field)) {
field = "0";
}
int sensitivity = 0;
switch (Integer.parseInt(field)) {
case 0://olNormal
sensitivity = Event.CLASS_PUBLIC;
break;
case 2://olPrivate
sensitivity = Event.CLASS_PRIVATE;
break;
}
// this code caused an IndexOutOfBoundsException
//event.removeValue(BlackBerryEvent.CLASS, 0);
event.addInt(BlackBerryEvent.CLASS, Event.ATTR_NONE, sensitivity);
}
//bugfix 869b BusyStatus
if (list.isSupportedField(BlackBerryEvent.FREE_BUSY)) {
field = getValue(eventMap, BUSY_STATUS);
/*
* to avoid a NumberFormatException
* parsing an empty string to obtain
* an int in the following switch
*/
if ("".equals(field)) {
/*
* olFree as default when <BusyStatus>
* is empty or even not present in the
* SIF-E message
*/
field = "0";
}
int status = 0;
switch (Integer.parseInt(field)) {
case 0://olFree
status = BlackBerryEvent.FB_FREE;
break;
case 1://olTentative
status = BlackBerryEvent.FB_TENTATIVE;
break;
case 2://olBusy
status = BlackBerryEvent.FB_BUSY;
break;
case 3://olOutOfOffice
status = BlackBerryEvent.FB_OUT_OF_OFFICE;
break;
}
event.removeValue(BlackBerryEvent.FREE_BUSY, 0);
event.addInt(BlackBerryEvent.FREE_BUSY, BlackBerryEvent.ATTR_NONE, status);
}
return event;
} catch (Exception e) {
StaticDataHelper.log(">>> EXCEPTION in XMLEventParser.modifyEvent(String) --> "
+ e.toString());
//logging with printStackTrace() isn't effective
e.printStackTrace();
return null;
}
}
return null;
}
/**
* Transforms the calendar event information from the
* storage of the device (also known as database) into
* a SIF-E XML string to pass to the server in the
* <code><Data></code> section of a SyncML message
*
* @param event A BlackBerry native calendar event
* wrapped in an Event class
* @return All event information in string format
*
* @see Event
* @see BlackBerryEvent
*/
public String toString(Event event) {
StringBuffer eventBuffer = new StringBuffer();
eventBuffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
eventBuffer.append("<appointment>\n");
boolean allday = false;
try {
allday = ((BlackBerryEvent)event).getBoolean(BlackBerryEvent.ALLDAY, 0);//20000928
} catch (Exception e) {
StaticDataHelper.log(">>> Exception in XMLEventParser.toString() --> Not set field: " + ALLDAY + "; e.toString(): " + e.toString());
}
appendField(eventBuffer,
(BlackBerryEvent)event,
BlackBerryEvent.SUMMARY,//107
SUMMARY);
appendField(eventBuffer,
(BlackBerryEvent)event,
BlackBerryEvent.LOCATION,//103
LOCATION);
appendField(eventBuffer,
(BlackBerryEvent)event,
BlackBerryEvent.NOTE,//104
NOTE);//<Body>
//bugfix 869a
appendSensitivity(eventBuffer);
//bugfix 869b
addBusyStatus(eventBuffer);
appendFieldDate(eventBuffer,
(BlackBerryEvent)event,
BlackBerryEvent.START, //106
START, //"Start"
allday);
//enhancement 868 (recurrence)
appendRecurrenceInformation(eventBuffer);
appendFieldBoolean(eventBuffer,
(BlackBerryEvent)event,
BlackBerryEvent.ALLDAY,//20000928
ALLDAY);
appendFieldDate(eventBuffer,
(BlackBerryEvent)event,
BlackBerryEvent.END, //102
END, //"End"
allday);
appendFieldAlarmMB(eventBuffer,
(BlackBerryEvent)event,
BlackBerryEvent.ALARM,//100
ALARM);
eventBuffer.append("</appointment>");
StaticDataHelper.log(">>> SIF-E -->\n" + eventBuffer.toString());
return eventBuffer.toString();
}
/**
* This method returns the current data for caching.
* It contains valid data after the event has been parsed.
*/
public Hashtable getCacheData() {
return cacheData;
}
/**
* This method sets the cache data for the current event.
* The data should contain the information retrieved from the cache,
* and this method must be called before the event is formatted for output.
*/
public void setCacheData(Hashtable data) {
cacheData=data;
}
/**
* This method appends the event information String Type
* supported by blackberry to a
* string buffer.
*
* @param StringBuffer: StringBuffer containing event information.
* @param Event: Blackberry event
* @param int: type of field
* @param String: Name of field
*/
private void appendField(StringBuffer eventBuffer,
Event event,
int field,
String tag) {
String value = null;
if (list.isSupportedField(field)) {
value = "";
try {
value = event.getString(field, 0);
} catch (Exception e) {
StaticDataHelper.log("Not set field: " + tag);
}
appendToEvent(eventBuffer, tag, value);
}
}
/**
* This method appends the event information String Type
* supported by blackberry to a
* string buffer.
*
* @param StringBuffer: StringBuffer containing event information.
* @param Event: Blackberry event
* @param int: type of field
* @param String: Name of field
*/
private void appendFieldBoolean(StringBuffer eventBuffer,
Event event,
int field,
String tag) {
boolean value = false;
if (list.isSupportedField(field)) {
try {
value = event.getBoolean(field, 0);
} catch (Exception e) {
StaticDataHelper.log("Not set field: " + tag);
}
if (value) {
appendToEvent(eventBuffer, tag, "1");
} else {
appendToEvent(eventBuffer, tag, "0");
}
}
}
/**
* The only time zone offset we need, is to generate a
* correct Calendar instance, that must be in the GMT
* time zone, starting from the current Date object of
* the calendar event in the local time zone. The only
* 'offset' we let here is the 'one day' offset to subtract
* from the end date provided by the device in case of
* an all day event (this is due to a RIM's API bug)
*
* @param eventBuffer The StringBuffer that will contain the
* calendar event information in SIF-E
* format that will be sent to the server
* @param event The native BlackBerry calendar event
* @param field The int native representation of a BlackBerry
* field in the calendar application
* @param tag A String indicating the date XML tag to be added
* to the SIF-E message: <Start> or <End>
* @param allday A boolean indicating if the calendar event
* is tagged as all day event
*
* @see #toString()
*/
private void appendFieldDate(StringBuffer eventBuffer ,
Event event ,
int field ,
String tag ,
boolean allday) {
/*
* The date contained in
* the Start or End Date
* field in the BlackBerry
* calendar application
* (depending on the value
* of the 'field' parameter)
*/
long value = 0;
/*
* The date to be inserted in
* the corresponding SIF-E element
* (<Start> or <End>).
* Depending on the fact that
* the calendar event can be
* allday or not-allday, the
* format will be "yyyy-MM-dd"
* or "yyyyMMdd'T'HHmmss'Z'"
*
*/
String formattedDate = null;
/*
* One of the two possible
* date formats, "yyyy-MM-dd"
* for allday calendar events
* or "yyyyMMdd'T'HHmmss'Z'"
* for non-allday calendar events
*/
String format = null;
/*
* The time zone offset, used
* to be subtracted from the
* local Date object before
* creating a Calendar instance
* in the GMT time zone
*/
long offset = 0;
/*
* A day (24 hours) to subtract
* from the End date of the
* event, otherwise (Start date)
* 0. This is due to an API's
* bug augmenting the End date
* by a day
*/
long oneDay = 0;//bug 1189
StaticDataHelper sdh = new StaticDataHelper();
String osVersion = sdh.getOS();
if (list.isSupportedField(field)) {
try {
value = event.getDate(field, 0);
StaticDataHelper.log("[LOG]Retrieving from device date to add to SIF-E: "
+ new Date(value).toString() + "[" + value + "]");
} catch (Exception e) {
StaticDataHelper.log(">>>Exception in XMLEventParser.appendFieldDate() "
+ "--> Field not set: " + tag + " e.toString(): "
+ e.toString());
}
//a Calendar in GMT
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));//getInstance()
//the date in the local time zone
Date evdate = new Date(value);
//the offset of the local time zone
offset = getTimeZoneOffSet(evdate);
//the calendar receives the local date as it was GMT
cal.setTime(new Date(value - offset));
// If the year is 1970, It can be to fix, check the cache.
if (cal.get(cal.YEAR) <= 1970) {
/*
* this check is to fix a bug introduced
* with the caching feature. In fact, the
* object cacheData is initialized only
* in the method parseEvent(), that is
* invoked only on data coming from the
* server to the client. When data is
* transferred only from the client to
* the server, the variable is not
* initialized, and a NullPointerException
* is thrown
*/
if (cacheData != null) {
Long cd = (Long)cacheData.get(tag);
if (cd != null) {
long cached = cd.longValue();
if (cached < 0) {
// Restore the original date prior to 01/01/1970
value = cached;
evdate = new Date(value);
cal.setTime(evdate);
}
}
}
}
if (allday) {
format = DATETIME_FORMAT_ALLDAY;//"yyyy-MM-dd"
// If it's an end date and it's past 01/01/1970,
// check if it has to be fixed.
if (isBuggy("allday") && tag.equals(END) && value > 0) { //allday <End>
long start = event.getDate(event.START, event.ATTR_NONE);
StaticDataHelper.log("[LOG]'value' -> " + new Date(value).toString());
StaticDataHelper.log("[LOG]'start' -> " + new Date(start).toString());
// If the event is changed, or it is new, we must fix
// the end before sending it out.
if (isEventChanged(start, value)) {//'value' is here the end date
oneDay = 24*60*60*1000;
}
}
} else {//not allday
format = DATETIME_FORMAT_UTC;//"yyyyMMdd'T'HHmmss'Z'"
}
SimpleDateFormat formatter = new SimpleDateFormat(format);
// FIXME: why cal.getTime().getTime() should be different from value?
sdh.log(">>>original date: " + new Date(cal.getTime().getTime()).toString());
formattedDate = formatter.format( new Date(cal.getTime().getTime() - oneDay) );
sdh.log(">>>fixed date: " + formattedDate);
/*
* this is to use the start date
* information of the <Start>
* element as content for the not supported
* <PatternStartDate> element
* in the method appendRecurrenceInformation()
*/
patternStart = formattedDate;
appendToEvent(eventBuffer, tag, formattedDate);
}//end check of the support for the application native field
}
/**
* This method appends the event information int Type
* supported by blackberry to a
* string buffer.
*
* @param StringBuffer: StringBuffer containing event information.
* @param Event: Blackberry event
* @param int: type of field
* @param String: Name of field.
* @return void.
*/
private void appendFieldInt(StringBuffer eventBuffer ,
Event event ,
int field ,
String tag ) {
// FIXME: this function should append the tag only if successful, shouldn't it?
// i.e.:
/*
if (list.isSupportedField(field)) {
try {
String value = String.valueOf(event.getInt(field, 0));
appendToEvent(eventBuffer, tag, value);
} catch (Exception e) {
StaticDataHelper.log("ERROR setting field: " + tag);
}
}
*/
String value = null ;
if (list.isSupportedField(field)) {
value = "";
try {
value = String.valueOf(event.getInt(field, 0));
} catch (Exception e) {
StaticDataHelper.log("Not set field: " + tag);
}
appendToEvent(eventBuffer, tag, value);
}
}
/**
* This method appends the event information int Type supported by blackberry to a
* string buffer.
*
* @param StringBuffer: StringBuffer containing event information.
* @param Event: Blackberry event
* @param int: type of field
* @param String: Name of field.
* @return void.
*/
private void appendFieldAlarmMB(StringBuffer eventBuffer ,
BlackBerryEvent event ,
int field ,
String tag ) {
String value = null ;
if (list.isSupportedField(field)) {
int reminder = 0;
value = "";
try {
reminder = (event.getInt(field, 0))/60;
value = String.valueOf(reminder);
} catch (Exception e) {
StaticDataHelper.log("Not set field: " + tag);
}
appendToEvent(eventBuffer, tag, value);
if (reminder > 0) {
appendToEvent(eventBuffer, ALARM_ENABLE, "1");
} else {
appendToEvent(eventBuffer, ALARM_ENABLE, "0");
}
}
}
/**
* This method appends the event information supported by blackberry to a
* string buffer
*
* @param StringBuffer: StringBuffer containing event information.
* @param String: name of tag associated with event information.
* @param String: value corresponding to tag.
* @return void
*/
private void appendToEvent(StringBuffer eventBuffer, String tag, String value) {
value = (value == null) ? "" : value;
if (value.length() > 0) {
value = StringTools.escapeXml(value);
}
eventBuffer.append("<").append(tag).append(">").append(value);
eventBuffer.append("</").append(tag).append(">\n");
}
/**
* Returns the value associated with a key in a
* hashtable contained in the passed
* <code>eventMap</code>
*
* @param eventMap Passed <code>Hashtable</code>
* @param key The name of a SIF-E element that acts
* as key for its value
* @return A String value associated with the passed key
* (an empty string if the SIF-E element exists
* and is empty, because this IS its value!). If
* in the message there isn't the searched SIF-E
* element (the passed key), a null value is
* returned from the hash table and again an empty
* string is returned by this method. This can
* cause problems by dealing with an empty string
* when e.g. a numeric string is expected by the
* consumer of this method to be parsed into an int
*/
private String getValue(Hashtable eventMap, String key) {
Object val = eventMap.get(key);
String value = val == null ? "" : val.toString();
return value.trim();
}
/**
* Checks if the field and attibute are supported by the event list
* of the BlackBerry calendar application
*
* @param int: field
* @param int: attribute
* @param String: value
* @return boolean:returns true if both the field and attribute
* associated with it r supported
*/
private boolean isSupportedAttributedField(int field, int attribute, String value) {
return isSupportedField(field, value) && list.isSupportedAttribute(field, attribute);
}
/**
* Checks if the type of field is supported by the event list
* of the BlackBerry calendar application
*
* @param int type
* @param String value
* @return boolean returns true if event list supports this field
*/
private boolean isSupportedField(int type, String value) {//value is never used
return list.isSupportedField(type);
}
/**
* This method creates a hash table with
* keys and values associated with
* the BlackBerry's event list attributes
*
* @param eventString The appointment data
* content ({@code <appointment>})
* @return A hashtable
* @throws SyncException if no correct event element in SIF-E comes from the
* server
*/
/*
private Hashtable buildMapFromXML(String eventString) throws SyncException {
int startMarkerIndex = eventString.indexOf(START_MARKER);//<appointment>
int endMarkerIndex = eventString.indexOf(END_MARKER);//</appointment>
if (startMarkerIndex == -1 || endMarkerIndex == -1) {
throw new SyncException("Improper data from the server");
//Dialog.inform("Improper data from the server.");
//return null;
}
//Check if xml is got an utf-8 encoding in header. TODO: Generalize to other encodings.
boolean is_utf8 = (eventString.substring(0, startMarkerIndex).toLowerCase().indexOf("encoding=\"utf-8\"") != -1);
Hashtable eventMap = new Hashtable();
boolean readNothing = true;
boolean readOpenTag = false;
boolean readData = false;
int openTagPos = 0;
int dataPos = 0;
String tagName = "";
String data = "";
//TODO: Handle CDATA
for(int i=startMarkerIndex + 13; i< endMarkerIndex; i++){
char c = eventString.charAt(i);
switch(c){
case '<':
if(readNothing){
openTagPos = i+1;
readOpenTag = true;
readNothing = false;
}
else if(readData){
int p = eventString.indexOf("</" + tagName + '>', i);
if(p != -1){
data = eventString.substring(dataPos, i);
if(is_utf8){
try{
data = new String(data.getBytes(), "UTF-8");
}
catch (java.io.UnsupportedEncodingException e){
Log.debug(e.toString());
}
}
i += tagName.length() + 2;
readData = false;
dataPos = 0;
readNothing = true;
data = unEscapeXML(data);
//Insert into map
eventMap.put(tagName, data);
}
else {
//Tag mismatch
//TODO:Exception
}
}
else {
//Unexpected <
//TODO:Exception
}
break;
case '/':
if(readOpenTag){
//<Tag />
readOpenTag = false;
readNothing = true;
openTagPos = 0;
i++;
}
break;
case ' ':
if(readOpenTag){
tagName = eventString.substring(openTagPos, i);
int p = eventString.indexOf('>', i);
if(p != -1){
if(eventString.substring(i,p).indexOf('/') != -1){
//<Tag />
readOpenTag = false;
readNothing = true;
openTagPos = 0;
i = p;
}
else {
//Finished reading open tag
i = p;
readOpenTag = false;
readData = true;
dataPos = i+1;
}
}
else {
//Tag not closed
//TODO:Exception
}
}
break;
case '>':
if(readOpenTag){
tagName = eventString.substring(openTagPos, i);
readOpenTag = false;
readData = true;
dataPos = i+1;
}
break;
default:
}
}
return eventMap;
}
*/
/**
* Get time from date in "yyyyMMddTHHmmssZ" String format
*
* @param field date in "yyyyMMddTHHmmssZ" String format
* @return A 'long' time in milliseconds from the Epoch
*/
private long getDate(String field) {
int day = 0 ;
int month = 0 ;
int year = 0 ;
int hour = 0 ;
int minute = 0 ;
int second = 0 ;
long offset = 0 ;
Calendar date = null ;
day = Integer.parseInt (field.substring( 6 , 8 ));
month = Integer.parseInt (field.substring( 4 , 6 ));
year = Integer.parseInt (field.substring( 0 , 4 ));
hour = Integer.parseInt (field.substring( 9 , 11 ));
minute = Integer.parseInt (field.substring( 11 , 13 ));
second = Integer.parseInt (field.substring( 13 , 15 ));
date = Calendar.getInstance();
date.set(Calendar.DAY_OF_MONTH , day ) ;
date.set(Calendar.MONTH , month - 1 ) ;
date.set(Calendar.YEAR , year ) ;
date.set(Calendar.HOUR_OF_DAY , hour ) ;
date.set(Calendar.MINUTE , minute ) ;
date.set(Calendar.SECOND , second ) ;
return date.getTime().getTime() + getTimeZoneOffSet(date.getTime());
}
/**
* Get time (a {@code long} value that holds the
* number of milliseconds since midnight GMT, January
* 1, 1970) from date in "yyyy-MM-dd" String format
*
* @param field Date in "yyyy-MM-dd" String format
* @return time at 00:00:00 from date
*/
private long getAlldayDateStart(String field) {
int day = 0;
int month = 0;
int year = 0;
Calendar date = null;
day = Integer.parseInt(field.substring(8, 10));
month = Integer.parseInt(field.substring(5, 7));
year = Integer.parseInt(field.substring(0, 4));
date = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
//date = Calendar.getInstance();
date.set(Calendar.DAY_OF_MONTH , day );
date.set(Calendar.MONTH , month - 1);
date.set(Calendar.YEAR , year );
date.set(Calendar.HOUR_OF_DAY , 0 );
date.set(Calendar.MINUTE , 0 );
date.set(Calendar.SECOND , 0 );
StaticDataHelper.log("getAlldayDateStart(:String) --> date.getTime(): " + date.getTime());
StaticDataHelper.log("getAlldayDateStart(:String) --> date.getTime().getTime(): " + date.getTime().getTime());
/*
* e.g.
*
* date.getTime() --> Tue Apr 05 00:00:00 GMT 1966
* (J2SE: date.toString())
*
* date.getTime().getTime() --> -118108799702
* (J2SE: date.getTime())
*
* This behaviour is different from J2SE, where
* long cannot be dereferenced (date.getTime().
* getTime() is in J2SE not possible)
*/
long time = date.getTime().getTime();
return time;
}
/**
* Get time from date in "yyyy-MM-dd" String format
* @param field date in "yyyy-MM-dd" String format
* @return time at 23:59:59 from date
*/
private long getAlldayDateEnd(String field) {
int day = 0 ;
int month = 0 ;
int year = 0 ;
int offset = 0 ;
Calendar date = null ;
StaticDataHelper sdh = new StaticDataHelper();
String osVersion = sdh.getOS();
//removed fixing bug 867
//offset = 24 * 60 * 60 * 1000;
day = Integer.parseInt(field.substring(8, 10));//xxxxxxxx10
month = Integer.parseInt(field.substring(5, 7));//xxxxx05xxx
year = Integer.parseInt(field.substring(0, 4));//1971xxxxxx
date = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
//date = Calendar.getInstance();
date.set(Calendar.DAY_OF_MONTH , day );
date.set(Calendar.MONTH , month - 1);
date.set(Calendar.YEAR , year );
date.set(Calendar.HOUR_OF_DAY , 23 );//00
date.set(Calendar.MINUTE , 59 );//00
date.set(Calendar.SECOND , 00 );//00 from 59 to 00 bugfix 1189
StaticDataHelper.log("getAlldayDateEnd(:String) --> date.getTime(): " + date.getTime());
StaticDataHelper.log("getAlldayDateEnd(:String) --> date.getTime().getTime(): " + date.getTime().getTime());
return date.getTime().getTime();//+ offset;
}
/**
* return seconds from minutes in String format
* @param field minutes in String format
* @return seconds
*/
private int getAlarmSecondsBefore(String field)
throws NumberFormatException {
int alarmMinutesBefore = Integer.parseInt(field);
return alarmMinutesBefore;
}
/**
* @param date date to read offset
* @return timezone offset
*/
private long getTimeZoneOffSet(Date date) {
Calendar now = null;
TimeZone zn = null;
long offset = 0;
/*
* Gets a calendar using the
* default time zone and locale
*/
now = Calendar.getInstance();
now.setTime(date);
/*
* creates a TimeZone based
* on the time zone where the
* program is running
*/
zn = TimeZone.getDefault();
/*
* the offset to add to GMT to
* get local time, modified in
* case of daylight savings
*/
offset = zn.getOffset(1 ,//era AD
now.get(Calendar.YEAR) ,
now.get(Calendar.MONTH) ,
now.get(Calendar.DAY_OF_MONTH) ,
now.get(Calendar.DAY_OF_WEEK) ,
now.get(Calendar.HOUR_OF_DAY) * 60 * 60 * 1000 +//the milliseconds
now.get(Calendar.MINUTE) * 60 * 1000 +//in day in standard
now.get(Calendar.SECOND) * 1000 );//local time
StaticDataHelper.log("+++ In XMLEventParser.getTimeZoneOffSet(...). Offset: " + offset);
StaticDataHelper.log("[DEBUG]zn.getRawOffset() --> " + zn.getRawOffset());
return offset;
}
/**
* Checks the OS version of the device
* @param os A String representing the
* actual OS of the device
* @return true if OS version >= 4.1.0
*/
private boolean checkOS(String os)
{
// bugfix 870 ------------------------------------
if (os == null || os.length() == 0)
{
StaticDataHelper sdh = new StaticDataHelper();
sdh.setOS();
os = sdh.getOS();
}
if (os == null)
{
return false;
}
// -----------------------------------------------
int major = Integer.valueOf(os.substring(0,1)).intValue();
int middle = Integer.valueOf(os.substring(2,3)).intValue();
int minor = 0;
if (os.length() > 4)
{
minor = Integer.valueOf(os.substring(4,5)).intValue();
}
if ((major > 4) || (major == 4 && middle >= 1))
{
return true;
}
else
{
return false;
}
}
//enhancement 868 (recurrence)
private void appendRecurrenceInformation(StringBuffer eventBuffer) {
int noEnd = 1;//so 'true' is <NoEndDate>1</NoEndDate>
int r = 0;//value for the <IsRecurring> SIF-E element
RepeatRule rule = event.getRepeat();
if (rule == null) {
/*
* if there isn't any RepeatRule object
* no recurrence is set:
* <IsRecurring>0</IsRecurring>
*/
r = 0;
eventBuffer.append("<" + IS_RECURRING + ">" +
r +
"</" + IS_RECURRING + ">");
} else {
/*
* if a RepeatRule object exists,
* then we have to build the appropriate
* SIF-E element (<IsRecurring>1</IsRecurring>)
* and to extract all available recurrence
* information from that object
*/
r = 1;//appointment is recurring
eventBuffer.append("<" + IS_RECURRING + ">" +
r +
"</" + IS_RECURRING + ">");
int[] fieldsArray = rule.getFields();
//DEBUG
StaticDataHelper.log("[DEBUG]Fields in RepeatRule object:");
for (int i = 0; i < fieldsArray.length; i++) {
StaticDataHelper.log(i + " -> " + fieldsArray[i]);
}
for (int i = 0; i < fieldsArray.length; i++) {
StaticDataHelper.log("[DEBUG]" + i + " --> " + fieldsArray[i]);
switch (fieldsArray[i]) {
//<RecurrenceType>
case RepeatRule.FREQUENCY://0
StaticDataHelper.log("[DEBUG]Found FREQUENCY (" + fieldsArray[i] + ")");
int type = 0;
//OlRecurrenceType
switch (rule.getInt(RepeatRule.FREQUENCY)) {
case RepeatRule.DAILY:
type = 0;//olRecursDaily
break;
case RepeatRule.WEEKLY:
type = 1;//olRecursWeekly
break;
case RepeatRule.MONTHLY:
try {
rule.getInt(RepeatRule.WEEK_IN_MONTH);
} catch (FieldEmptyException f) {
type = 2;//olRecursMonthly
break;
}
type = 3;//olRecursMonthlyNth
break;
case RepeatRule.YEARLY:
try {
rule.getInt(RepeatRule.WEEK_IN_MONTH);
} catch (FieldEmptyException f) {
type = 5;//olRecursYearly
break;
}
type = 6;//olRecursYearlyNth
break;
}
eventBuffer.append("<" + RECURRENCE_TYPE + ">" +
type +
"</" + RECURRENCE_TYPE + ">");
break;
//<Interval>
case RepeatRule.INTERVAL://128
StaticDataHelper.log("[DEBUG]Found INTERVAL (" + fieldsArray[i] + ")");
int interval = rule.getInt(RepeatRule.INTERVAL);
eventBuffer.append("<" + INTERVAL + ">" +
interval +
"</" + INTERVAL + ">");
break;
//<PatternEndDate>
case RepeatRule.END://64
StaticDataHelper.log("[DEBUG]Found END (" + fieldsArray[i] + ")");
long endDate = rule.getDate(RepeatRule.END);
SimpleDateFormat formatter = new SimpleDateFormat(DATETIME_FORMAT_UTC);
String formattedDate = formatter.formatLocal(endDate);
//e.g. <PatternEndDate>20080504T150000Z</PatternEndDate>
eventBuffer.append("<" + PATTERN_END_DATE + ">" +
formattedDate +
"</" + PATTERN_END_DATE + ">");
//<NoEndDate>0</NoEndDate>
noEnd = 0;
break;
/*
* Something like <PatternStartDate>
* isn't supported by the RIM's API
*/
//<DayOfWeekMask>
case RepeatRule.DAY_IN_WEEK://2
StaticDataHelper.log("[DEBUG]Found DAY_IN_WEEK (" + fieldsArray[i] + ")");
//OlDaysOfWeek
int valueFromDevice = rule.getInt(RepeatRule.DAY_IN_WEEK);
int mask = 0;
//olSunday 1
if ((valueFromDevice & RepeatRule.SUNDAY) == RepeatRule.SUNDAY) {
mask = mask + 1;
}
//olMonday 2
if ((valueFromDevice & RepeatRule.MONDAY) == RepeatRule.MONDAY) {
mask = mask + 2;
}
//olTuesday 4
if ((valueFromDevice & RepeatRule.TUESDAY) == RepeatRule.TUESDAY) {
mask = mask + 4;
}
//olWednesday 8
if ((valueFromDevice & RepeatRule.WEDNESDAY) == RepeatRule.WEDNESDAY) {
mask = mask + 8;
}
//olThursday 16
if ((valueFromDevice & RepeatRule.THURSDAY) == RepeatRule.THURSDAY) {
mask = mask + 16;
}
//olFriday 32
if ((valueFromDevice & RepeatRule.FRIDAY) == RepeatRule.FRIDAY) {
mask = mask + 32;
}
//olSaturday 64
if ((valueFromDevice & RepeatRule.SATURDAY) == RepeatRule.SATURDAY) {
mask = mask + 64;
}
/*
* the Outlook plug-in doesn't accept
* an appointment in SIF-E format where
* the <DayOfWeekMask> element is given
* for events different from 'daily',
* 'weekly' or 'yearly'
*/
if (rule.getInt(RepeatRule.FREQUENCY) == RepeatRule.DAILY ||
rule.getInt(RepeatRule.FREQUENCY) == RepeatRule.WEEKLY ||
rule.getInt(RepeatRule.FREQUENCY) == RepeatRule.YEARLY) {
eventBuffer.append("<" + DAY_OF_WEEK_MASK + ">" +
mask +
"</" + DAY_OF_WEEK_MASK + ">");
} else {
/*
* in case of a monthly event,
* Outlook seems to require that
* the element is present but with
* value set to '0'
*/
eventBuffer.append("<" + DAY_OF_WEEK_MASK + ">" +
0 +
"</" + DAY_OF_WEEK_MASK + ">");
}
break;
//<Occurrences>
case RepeatRule.COUNT://32
StaticDataHelper.log("[DEBUG]Found COUNT (" + fieldsArray[i] + ")");
int count = rule.getInt(RepeatRule.COUNT);
eventBuffer.append("<" + OCCURRENCES + ">" +
count +
"</" + OCCURRENCES + ">");
break;
//<DayOfMonth>
case RepeatRule.DAY_IN_MONTH://1
StaticDataHelper.log("[DEBUG]Found DAY_IN_MONTH (" + fieldsArray[i] + ")");
//1-31
int dayInMonth = rule.getInt(RepeatRule.DAY_IN_MONTH);
eventBuffer.append("<" + DAY_OF_MONTH + ">" +
dayInMonth +
"</" + DAY_OF_MONTH + ">");
break;
//<Instance> (limited to 1-5 due to SIF-E limitations)
case RepeatRule.WEEK_IN_MONTH:
StaticDataHelper.log("[DEBUG]Found WEEK_IN_MONTH (" + fieldsArray[i] + ")");
int week = 0;
switch (rule.getInt(RepeatRule.WEEK_IN_MONTH)) {
case RepeatRule.FIRST://1
week = 1;
break;
case RepeatRule.SECOND://2
week = 2;
break;
case RepeatRule.THIRD://4
week = 3;
break;
case RepeatRule.FOURTH://8
week = 4;
break;
/*
* in Outlook 2003: 'last'
* in the BlackBerry: 'last'
*/
case RepeatRule.FIFTH://16
week = 5;
break;
/*
* these values of the Sun Java API
* are not supported by the BlackBerry
* and the Outlook plug-ins
*
case RepeatRule.LAST://32
week = 32;
break;
case RepeatRule.SECONDLAST://64
week = 64;
break;
case RepeatRule.THIRDLAST://128
week = 128;
break;
case RepeatRule.FOURTHLAST://256
week = 256;
break;
case RepeatRule.FIFTHLAST://512
week = 512;
break;
*/
}
eventBuffer.append("<" + INSTANCE + ">" +
week +
"</" + INSTANCE + ">");
break;
//<MonthOfYear> 1-12
case RepeatRule.MONTH_IN_YEAR:
StaticDataHelper.log("[DEBUG]Found MONTH_IN_YEAR (" + fieldsArray[i] + ")");
int month = 0;
int monthOfYear = rule.getInt(RepeatRule.MONTH_IN_YEAR);
switch (monthOfYear) {
case RepeatRule.JANUARY:
month = 1;
break;
case RepeatRule.FEBRUARY:
month = 2;
break;
case RepeatRule.MARCH:
month = 3;
break;
case RepeatRule.APRIL:
month = 4;
break;
case RepeatRule.MAY:
month = 5;
break;
case RepeatRule.JUNE:
month = 6;
break;
case RepeatRule.JULY:
month = 7;
break;
case RepeatRule.AUGUST:
month = 8;
break;
case RepeatRule.SEPTEMBER:
month = 9;
break;
case RepeatRule.OCTOBER:
month = 10;
break;
case RepeatRule.NOVEMBER:
month = 11;
break;
case RepeatRule.DECEMBER:
month = 12;
break;
}
eventBuffer.append("<" + MONTH_OF_YEAR + ">" +
month +
"</" + MONTH_OF_YEAR + ">");
break;
}//end external switch
}//end for loop
/*
* the pattern start date isn't accessible
* through the RIM's API, so we take the
* same value as the start date
*/
eventBuffer.append("<" + PATTERN_START_DATE + ">" +
patternStart +
"</" + PATTERN_START_DATE + ">");
/*
* <NoEndDate>1</NoEndDate> if
* <PatternEndDate> doesn't exist.
* If <PatternEndDate> does exist,
* noEnd is set to 0 in the
* appropriate section above
*/
eventBuffer.append("<" + NO_END_DATE + ">" +
noEnd +
"</" + NO_END_DATE + ">");
}//end else
}
//end recurrence implementation
/*
* bugfix 869a
* (but Event.CLASS is unfortunately unsupported:
* to not pollute the data coming to the server from
* an other client, it is important that this tag
* is not added to the SIF-E to the server!)
*
*/
private void appendSensitivity(StringBuffer eventBuffer) {
/*
* using unsupported Event.CLASS field
* causes an UnsupportedFieldException
*/
if (list.isSupportedField(Event.CLASS)) {
int c = 0;
//catching the IndexOutOfBoundException is just a workaround
//in order to avoid the impossibility to sync calendar events
//that were created as 'public' on the BlackBerry device
try {
//commented to avoid the exception now (caused by invoking getInt when CLASS == 202)
//StaticDataHelper.log(">>> Sensitivity class --> " + event.getInt(Event.CLASS, 0));
switch (event.getInt(Event.CLASS, 0)) {
case Event.CLASS_PUBLIC://202
c = 0;//<Sensitivity>0</Sensitivity>
break;
case Event.CLASS_PRIVATE://201
c = 2;//<Sensitivity>2</Sensitivity>
break;
}
} catch (IndexOutOfBoundsException ioobe) {
StaticDataHelper.log(">>> Exception, because the event was created on the BB as public and not private: " +
ioobe.toString());
c = 0;
}
eventBuffer.append("<" + SENSITIVITY + ">" + c + "</" + SENSITIVITY + ">");
} else {
StaticDataHelper.log(">>> SIF-E <" + SENSITIVITY + "> tag not supported by this device");
}
}
//bugfix 869b
private void addBusyStatus(StringBuffer eventBuffer)
{
if (list.isSupportedField(BlackBerryEvent.FREE_BUSY))//20000929
{
int busyStatus = 0;
if (event.countValues(BlackBerryEvent.FREE_BUSY) > 0)
{
switch (event.getInt(BlackBerryEvent.FREE_BUSY, 0))
{
case BlackBerryEvent.FB_FREE:
busyStatus = 0;//olFree
break;
case BlackBerryEvent.FB_TENTATIVE:
busyStatus = 1;//olTentative
break;
case BlackBerryEvent.FB_OUT_OF_OFFICE:
busyStatus = 3;//olOutOfOffice
break;
}
}
else
{
//no values in field FREE_BUSY
busyStatus = 2;//olBusy
}
eventBuffer.append("<" + BUSY_STATUS + ">" +
busyStatus +
"</" + BUSY_STATUS + ">");
}
}
// If the year is prior to 1970, set it to 1970.
private long adjustDateToEpoch(long date) {
Calendar cal = Calendar.getInstance();
cal.setTime(new Date(date));
if(cal.get(cal.YEAR) < 1970){
cal.set(cal.YEAR, 1970);
}
StaticDataHelper.log("[DEBUG]In adjustDateToEpoch() the adjusted returned date is " + cal.getTime().getTime());
return cal.getTime().getTime();
}
/**
* Returns <code>true</code> if the event is new
* or has changed the dates on the device.
*
* If one of the dates is different in the cache,
* or if the cache is not present, it means that the
* event is new or changed
*/
private boolean isEventChanged(long start, long end) {
if (cacheData == null) {
return true;
}
Long cStart = (Long)cacheData.get(START);
Long cEnd = (Long)cacheData.get(END);
if (cStart == null || cEnd == null) {
return true; // no cache, is new
}
StaticDataHelper.log("[DEBUG] cStart: " + new Date(cStart.longValue()));
StaticDataHelper.log("[DEBUG] start: " + new Date(start).toString());
StaticDataHelper.log("[DEBUG] cEnd: " + new Date(cEnd.longValue()));
StaticDataHelper.log("[DEBUG] end: " + new Date(end).toString());
StaticDataHelper.log("cStart = " + cStart.longValue());
StaticDataHelper.log("start = " + start);
StaticDataHelper.log("cEnd = " + cEnd.longValue());
StaticDataHelper.log("end = " + end);
//Date cStartObj = new Date(cStart.longValue());
//Date startObj = new Date(start);
//Date cEndObj = new Date(cEnd.longValue());
//Date endObj = new Date(end);
//boolean ret = !(cStartObj.equals(startObj) && cEndObj.equals(endObj));
boolean ret = !(cStart.longValue() == start && cEnd.longValue() == end);
StaticDataHelper.log("[DEBUG] ret: " + ret);
return ret;
}
/**
* Based on a property file containing all known issues
* related to all BlackBerry devices...
*/
private boolean isBuggy(String knownIssue) {
return true;// it has to be false
}
/**
* Takes the recurrence information
* in SIF-E format from the SyncML
* message and adds it to the current
* event.
* <p>This method is used both with
* add and modify functions, depending
* on the value of the {@code modify}
* variable
*
*/
private void parseRecurrence(Hashtable eventMap) {
RepeatRule rule = null;
if (modify) {
rule = event.getRepeat();
} else {
rule = new RepeatRule();
}
//<RecurrenceType>
String recurrenceType = getValue(eventMap, RECURRENCE_TYPE);//1-6
int type = 0;
if (!"".equals(recurrenceType)) {
switch (Integer.parseInt(recurrenceType)) {
case 0://olRecursDaily
type = RepeatRule.DAILY;
break;
case 1://olRecursWeekly
type = RepeatRule.WEEKLY;
break;
case 2://olRecursMonthly
type = RepeatRule.MONTHLY;
break;
case 3://olRecursMonthlyNth
type = RepeatRule.MONTHLY;
break;
case 5://olRecursYearly
type = RepeatRule.YEARLY;
break;
case 6://olRecursYearlyNth
type = RepeatRule.YEARLY;
return; //not supported
//break;
}
rule.setInt(RepeatRule.FREQUENCY, type);
}
//<Interval>
String interval = getValue(eventMap, INTERVAL);//e.g. 2
if ((!"".equals(interval)) && (!"0".equals(interval))) {
rule.setInt(RepeatRule.INTERVAL,
Integer.parseInt(interval));
}
//<MonthOfYear>
String monthOfYear = getValue(eventMap, MONTH_OF_YEAR);//1-12
int month = 0;
if (!"".equals(monthOfYear)) {
switch (Integer.parseInt(monthOfYear)) {
case 1:
month = RepeatRule.JANUARY;
break;
case 2:
month = RepeatRule.FEBRUARY;
break;
case 3:
month = RepeatRule.MARCH;
break;
case 4:
month = RepeatRule.APRIL;
break;
case 5:
month = RepeatRule.MAY;
break;
case 6:
month = RepeatRule.JUNE;
break;
case 7:
month = RepeatRule.JULY;
break;
case 8:
month = RepeatRule.AUGUST;
break;
case 9:
month = RepeatRule.SEPTEMBER;
break;
case 10:
month = RepeatRule.OCTOBER;
break;
case 11:
month = RepeatRule.NOVEMBER;
break;
case 12:
month = RepeatRule.DECEMBER;
break;
}
}
/*
* the Outlook plug-in generates
* sometimes an element like this:
* <MonthOfYear>0</MonthOfYear>. '0'
* can't be added to the rule object
* of the BlackBerry
*/
if (month != 0) {
rule.setInt(RepeatRule.MONTH_IN_YEAR, month);
}
//<DayOfMonth> 1-31
String dayOfMonth = getValue(eventMap, DAY_OF_MONTH);//e.g. 7
if ((!"".equals(dayOfMonth)) && (Integer.parseInt(dayOfMonth) != 0)) {
rule.setInt(RepeatRule.DAY_IN_MONTH,
Integer.parseInt(dayOfMonth));
}
// <DayOfWeekMask> start
String dayOfWeekMaskAsString = getValue(eventMap, DAY_OF_WEEK_MASK);//1-64
if (!"".equals(dayOfWeekMaskAsString)) {
int dayOfWeekMask = Integer.parseInt(dayOfWeekMaskAsString);
int mask = 0;
if ((dayOfWeekMask & olMonday) == olMonday) {
mask = mask + RepeatRule.MONDAY;
}
if ((dayOfWeekMask & olTuesday) == olTuesday) {
mask = mask + RepeatRule.TUESDAY;
}
if ((dayOfWeekMask & olWednesday) == olWednesday) {
mask = mask + RepeatRule.WEDNESDAY;
}
if ((dayOfWeekMask & olThursday) == olThursday) {
mask = mask + RepeatRule.THURSDAY;
}
if ((dayOfWeekMask & olFriday) == olFriday) {
mask = mask + RepeatRule.FRIDAY;
}
if ((dayOfWeekMask & olSaturday) == olSaturday) {
mask = mask + RepeatRule.SATURDAY;
}
if ((dayOfWeekMask & olSunday) == olSunday) {
mask = mask + RepeatRule.SUNDAY;
}
rule.setInt(RepeatRule.DAY_IN_WEEK, mask);
}// <DayOfWeekMask> end
//<Instance> start
String instance = getValue(eventMap, INSTANCE);//e.g. 2
int week = 0;
if (!"".equals(instance)) {
switch (Integer.parseInt(instance)) {
case 1:
week = RepeatRule.FIRST;
break;
case 2:
week = RepeatRule.SECOND;
break;
case 3:
week = RepeatRule.THIRD;
break;
case 4:
week = RepeatRule.FOURTH;
break;
case 5:
week = RepeatRule.FIFTH;
break;
}
rule.setInt(RepeatRule.WEEK_IN_MONTH, week);
}//<Instance> end
//<PatternStartDate>
String patternStartDate = getValue(eventMap, PATTERN_START_DATE);//e.g. 20060706T220000Z
//This is currently not supported by the RIM API
//<NoEndDate>
String noEndDate = getValue(eventMap, NO_END_DATE);//e.g. 0 (false)
//This is not managed in input
//<Occurences>
String occurrences = getValue(eventMap, OCCURRENCES);//e.g. 33
if (!"".equals(occurrences)) {
rule.setInt(RepeatRule.COUNT,
Integer.parseInt(occurrences));
}
//<PatternEndDate>
String patternEndDate = getValue(eventMap, PATTERN_END_DATE);//e.g. 20111106T230000Z
//The field value is currently limited to RepeatRule.END (RIM API)
if (!"".equals(patternEndDate)) {
rule.setDate(RepeatRule.END, getDate(patternEndDate));
} else {
rule.setDate(RepeatRule.END, 0l);
}
/*
* though we're modifying an already
* existing appointment and 'rule'
* references an already existing
* object, we do need to set it into
* the event anyway
*/
event.setRepeat(rule);
}//end parseRecurrence()
}