gt:                 JSONObject item = updatedArray.getJSONObject(i);
context: /*
   * 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.sapisync;
  
  import java.util.Hashtable;
  import java.util.Date;
  
  import com.funambol.org.json.me.JSONArray;
  import com.funambol.org.json.me.JSONException;
  import com.funambol.org.json.me.JSONObject;
  import com.funambol.sync.SyncException;
  import com.funambol.sync.SyncItem;
  import com.funambol.sync.SyncSource;
  import com.funambol.sync.TwinDetectionSource;
  import com.funambol.sapisync.source.JSONSyncSource;
  import com.funambol.sapisync.source.JSONSyncItem;
  import com.funambol.sync.Filter;
  import com.funambol.sync.SyncFilter;
  import com.funambol.util.Log;
  
  
  public class SapiSyncStrategy {
  
      private static final String TAG_LOG = "SapiSyncStrategy";
  
      private static final int FULL_SYNC_DOWNLOAD_LIMIT = 300;
  
      private JSONArray addedArray   = null;
      private JSONArray updatedArray = null;
      private JSONArray deletedArray = null;
  
      private Hashtable localUpdated;
      private Hashtable localDeleted;
      private Hashtable localRenamed;
  
      private SapiSyncHandler sapiSyncHandler;
      private JSONObject removedItemMarker;
      private long downloadNextAnchor;
      private String addedServerUrl = null;
      private String updatedServerUrl = null;
      private long clientServerTimeDifference = 0;
      private SapiSyncUtils utils = new SapiSyncUtils();
  
      public SapiSyncStrategy(SapiSyncHandler sapiSyncHandler, JSONObject removedItemMarker) {
          this.sapiSyncHandler = sapiSyncHandler;
          this.removedItemMarker = removedItemMarker;
      }
  
      /**
       * This method computes the set of items to be downloaded from the server in
       * an incremental sync.
       * The set of data to be downloaded depends on many things, including the
       * changes made locally. After this method the src update/delete items have
       * been consumed and the getNextNewItem and getNextUpdItem will return null.
       */
      public void prepareSync(SyncSource src, int downloadSyncMode, int uploadSyncMode,
                              boolean resume, MappingTable mapping, boolean incrementalDownload,
                              boolean incrementalUpload, Hashtable twins)
      throws SyncException, JSONException
      {
          // Check what is available on the server and what changed locally to
          // determine the list of items to be exchanged
          if (Log.isLoggable(Log.INFO)) {
              Log.info(TAG_LOG, "Computing changes set for fast sync");
          }
  
          addedArray   = null;
          updatedArray = null;
          deletedArray = null;
  
          // Get all the info on what is available on the server
          if (downloadSyncMode != SyncSource.NO_SYNC) {
              if (incrementalDownload) {
                  prepareSyncIncrementalDownload(src, mapping, twins);
              } else {
                  prepareSyncFullDownload(src, mapping, twins);
              }
          }
  
          // Get all the info on what changes are to be sent
          if (uploadSyncMode != SyncSource.NO_SYNC) {
              if (incrementalUpload) {
                  prepareSyncIncrementalUpload(src, mapping, twins);
              } else {
                  prepareSyncFullUpload(src, mapping, downloadSyncMode, incrementalDownload, twins);
              }
          } else {
              localUpdated = null;
              localDeleted = null;
              localRenamed = null;
          }
  
          // Resolve conflicts
          finalizePreparePhase(src, mapping, twins, incrementalDownload, incrementalUpload);
      }
  
      public JSONArray getServerAddedItems() {
          return addedArray;
      }
  
      public JSONArray getServerUpdatedItems() {
          return updatedArray;
      }
  
      public JSONArray getServerDeletedItems() {
          return deletedArray;
      }
  
      public long getDownloadNextAnchor() {
          return downloadNextAnchor;
      }
  
      public String getAddedServerUrl() {
          return addedServerUrl;
      }
  
      public String getUpdatedServerUrl() {
          return updatedServerUrl;
      }
  
      public void setClientServerTimeDifference(long clientServerTimeDifference) {
          this.clientServerTimeDifference = clientServerTimeDifference;
      }
  
      public void setSapiSyncHandler(SapiSyncHandler sapiSyncHandler) {
          this.sapiSyncHandler = sapiSyncHandler;
      }
  
      public Hashtable getLocalUpdates() {
          return localUpdated;
      }
  
      public Hashtable getLocalDeletes() {
          return localDeleted;
      }
  
      private void prepareSyncIncrementalDownload(SyncSource src, MappingTable mapping, Hashtable twins)
      throws SyncException, JSONException
      {
          String remoteUri = src.getConfig().getRemoteUri();
          SyncFilter syncFilter = src.getFilter();
          if(syncFilter != null && syncFilter.getIncrementalDownloadFilter() != null) {
              throw new UnsupportedOperationException("Not implemented yet");
          }
          SapiSyncAnchor sapiAnchor = (SapiSyncAnchor)src.getConfig().getSyncAnchor();
          if (Log.isLoggable(Log.TRACE)) {
              Log.trace(TAG_LOG, "Last download anchor is: " + sapiAnchor.getDownloadAnchor());
          }
          Date anchor = new Date(sapiAnchor.getDownloadAnchor());
  
          SapiSyncHandler.ChangesSet changesSet = null;
          try {
              changesSet = sapiSyncHandler.getIncrementalChanges(anchor, remoteUri);
          } catch (SapiException e) {
              String errorMessage = "Client error while getting incremental changes";
              utils.processCommonSapiExceptions(e, errorMessage, false);
              utils.processCustomSapiExceptions(e, errorMessage, true);
          }
              
          if (changesSet != null) {
              if (Log.isLoggable(Log.DEBUG)) {
                  Log.debug(TAG_LOG, "There are changes pending on the server");
              }
  
              // Use the above value as timestamp for the next sync
              downloadNextAnchor = changesSet.timeStamp;
  
              SapiSyncHandler.FullSet addedInfo   = null;
              SapiSyncHandler.FullSet updatedInfo = null;
              SapiSyncHandler.FullSet deletedInfo = null;
  
              addedInfo   = fetchItemsInfo(src, changesSet.added);
              updatedInfo = fetchItemsInfo(src, changesSet.updated);
              deletedArray = changesSet.deleted;
  
              if (addedInfo != null) {
                  addedArray = addedInfo.items;
                  addedServerUrl = addedInfo.serverUrl;
              } 
              if (updatedInfo != null) {
                  updatedArray = updatedInfo.items;
                  updatedServerUrl = updatedInfo.serverUrl;
              }
          }
      }
  
      private void prepareSyncIncrementalUpload(SyncSource src, MappingTable mapping, Hashtable twins)
      throws SyncException, JSONException
      {
          localUpdated = new Hashtable();
          localDeleted = new Hashtable();
          localRenamed = new Hashtable();
  
          JSONSyncItem localUpdatedItem = (JSONSyncItem)src.getNextUpdatedItem();
          while(localUpdatedItem != null) {
  
              if (localUpdatedItem.isItemKeyUpdated() && localUpdatedItem.getOldKey() != null) {
                  localRenamed.put(localUpdatedItem.getOldKey(), localUpdatedItem);
              }
  
              localUpdated.put(localUpdatedItem.getKey(), localUpdatedItem);
              localUpdatedItem = (JSONSyncItem)src.getNextUpdatedItem();
  
          }
  
          SyncItem localDeletedItem = src.getNextDeletedItem();
          while(localDeletedItem != null) {
              localDeleted.put(localDeletedItem.getKey(), localDeletedItem);
              localDeletedItem = src.getNextDeletedItem();
          }
      }
  
      private void prepareSyncFullUpload(SyncSource src, MappingTable mapping,
                                         int downloadSyncMode, boolean incrementalDownload, Hashtable twins)
      throws SyncException, JSONException {
  
          // In a full upload we need to know all the server items in order to
          // detect twins and avoid duplicates.
          // We do it only if we don't already perform a full download in this sync
  
          if (incrementalDownload || downloadSyncMode == SyncSource.NO_SYNC) {
              int offset = 0;
              boolean done = false;
              do {
                  SapiSyncHandler.FullSet fullSet = sapiSyncHandler.getItems(
                          src.getConfig().getRemoteUri(), getDataTag(src), null,
                          Integer.toString(FULL_SYNC_DOWNLOAD_LIMIT),
                          Integer.toString(offset), null);
                  if (fullSet != null && fullSet.items != null && fullSet.items.length() > 0) {
                      // This will find all the twins that will be skipped during the upload
                      discardTwinAndConflictFromList(src, fullSet.items, null, null,
                              fullSet.serverUrl, mapping, twins, true);
                      offset += fullSet.items.length();
                      if ((fullSet.items.length() < FULL_SYNC_DOWNLOAD_LIMIT)) {
                          done = true;
                      }
                  } else {
                      done = true;
                  }
              } while(!done);
          }
      }
  
      private void finalizePreparePhase(SyncSource src, MappingTable mapping, Hashtable twins,
                                        boolean incrementalDownload, boolean incrementalUpload)
      throws JSONException {
  
          // Now we have all the required information to decide what we need
          // to download/upload. It is here that we resolve conflicts and
          // twins
          if (addedArray != null) {
              // The server has items to send.
  
              // First of all check if this command is a real add or an update
              for(int i=0;i<addedArray.length();++i) {
                  JSONObject item = addedArray.getJSONObject(i);
                  String     guid = item.getString(SapiSyncManager.ID_FIELD);
  
                  String localItemId = mapping.getLuid(guid);
  
                  if (localItemId != null) {
  
                      ItemComparisonResult equal = compareItems(item, mapping);
  
                      if (equal.getIdentical()) {
                          if (Log.isLoggable(Log.INFO)) {
                              Log.info(TAG_LOG, "Server sent an add which already exists on client, ignore it");
                          }
                          addedArray.put(i, removedItemMarker);
                      } else {
                          // This is rather an update because the guid is already
                          // in the mapping table
                          if (Log.isLoggable(Log.INFO)) {
                              Log.info(TAG_LOG, "Turning an add into an update");
                          }
  
                          // Nullify this item
                          addedArray.put(i, removedItemMarker);
                          if (updatedArray == null) {
                              updatedArray = new JSONArray();
                              updatedServerUrl = addedServerUrl;
                          }
                          updatedArray.put(item);
  
                          // Update the item's properties according to what
                          // changed in this update
                          setUpdatedProperties(item, equal, mapping.getLuid(guid));
                      }
                  }
              }
          }
  
          // Now check if there is any update which is not an update, but
          // just an add instead (i.e. it does not exist in our mapping)
          if (updatedArray != null) {
              for(int i=0;i<updatedArray.length();++i) {
  