Package com.funambol.syncclient.blackberry.parser

Source Code of com.funambol.syncclient.blackberry.parser.XMLEventParser

/*
* 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: &lt;Start&gt; or &lt;End&gt;
     * @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( , ));
        month  = Integer.parseInt (field.substring( , ));
        year   = Integer.parseInt (field.substring( , ));
        hour   = Integer.parseInt (field.substring( , 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 - ) ;
        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(57));//xxxxx05xxx
        year   = Integer.parseInt(field.substring(04));//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()

}

TOP

Related Classes of com.funambol.syncclient.blackberry.parser.XMLEventParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.