/**
  * Synchronize a local instance repository to the db repository. Calling this method may modify
  * deleteMap Map in the FileAdaptor object.
  *
  * @param fileAdaptor the PersistenceAdaptor for the local instance repository.
  * @param dbAdaptor the PersistenceAdaptor for the database instance repository.
  * @return Key: delete, deleteInDB, new and/or changed; Values: A list of instances
  *     <p>Sideeffect: changes typeMap.
  */
 private Map synchronize(final XMLFileAdaptor fileAdaptor, final MySQLAdaptor dbAdaptor) {
   // Need another thread to display the progress
   final ProgressDialog progDialog = new ProgressDialog((JFrame) getOwner());
   progDialog.setSize(300, 180);
   // GKApplicationUtilities.center(progDialog);
   progDialog.setLocationRelativeTo(progDialog.getOwner());
   progDialog.setModal(true);
   final List syncClassList = getSynchronizingClassList(fileAdaptor);
   // Get the total instance counter
   Schema schema = fileAdaptor.getSchema();
   SchemaClass cls = null;
   long total = 0;
   try {
     total = getTotalInstanceCount(fileAdaptor);
   } catch (Exception e) {
     System.err.println("SyncrhonizationEngine.synchronize(): " + e);
     e.printStackTrace();
   }
   progDialog.totalBar.setMaximum((int) total);
   progDialog.totalBar.setMinimum(0);
   // Let the synchronizing work in another thread.
   final Map map = new HashMap();
   Thread t =
       new Thread() {
         public void run() {
           java.util.List newInstances = new ArrayList();
           java.util.List changedInstances = new ArrayList();
           java.util.List deletedInDBInstances = new ArrayList();
           java.util.List localHasMoreIEInstances = new ArrayList();
           Schema schema = fileAdaptor.getSchema();
           SchemaClass schemaClass = null;
           Collection instances = null;
           GKInstance instance = null;
           GKInstance dbCopy = null;
           int index = 0;
           int total = 0;
           // For comparsing
           InstanceComparer comparer = new InstanceComparer();
           try {
             for (Iterator it = syncClassList.iterator(); it.hasNext(); ) {
               if (isCancelled) break;
               schemaClass = (SchemaClass) it.next();
               instances = fileAdaptor.fetchInstancesByClass(schemaClass, false);
               if (instances == null || instances.size() == 0) continue;
               progDialog.clsLabel.setText("Scan class " + schemaClass.getName() + "...");
               progDialog.clsBar.setMinimum(0);
               progDialog.clsBar.setMaximum(instances.size());
               List existedIDs = new ArrayList(instances.size());
               for (Iterator it1 = instances.iterator(); it1.hasNext(); ) {
                 instance = (GKInstance) it1.next();
                 if (instance.getDBID().longValue() < 0) {
                   newInstances.add(instance);
                   typeMap.put(instance, NEW_KEY);
                 } else // Shell instances should not be skipped since a shell instance can still
                   // be deleted locally so existence checking is needed.
                   existedIDs.add(instance.getDBID());
               }
               if (existedIDs.size() == 0) continue;
               Collection existences = dbAdaptor.fetchInstances(schemaClass.getName(), existedIDs);
               if (existences.size() > 0) {
                 // Load all modified attribute
                 SchemaAttribute att = schemaClass.getAttribute("modified");
                 // Load all atributes in a batch
                 dbAdaptor.loadInstanceAttributeValues(existences, att);
               }
               index = 0;
               for (Iterator it1 = instances.iterator(); it1.hasNext(); ) {
                 if (isCancelled) break;
                 instance = (GKInstance) it1.next();
                 if (instance.getDBID().longValue() < 0) { // Handled by the previous loop
                   continue;
                 }
                 // The schema class might be changed. Use the
                 // top-leve class to fetch instance
                 dbCopy = dbAdaptor.fetchInstance(instance.getDBID());
                 if (dbCopy == null) {
                   deletedInDBInstances.add(instance);
                   typeMap.put(instance, DELETE_IN_DB_KEY);
                 }
                 // Don't care a shell instance
                 else {
                   if (!instance.isShell()) {
                     int tmp = comparer.compare(instance, dbCopy);
                     String typeKey = mapCompareResultToString(tmp);
                     if (typeKey == LOCAL_HAS_MORE_IE_KEY) {
                       localHasMoreIEInstances.add(instance);
                       typeMap.put(instance, typeKey);
                     } else if (typeKey != IS_IDENTICAL_KEY) {
                       changedInstances.add(instance);
                       typeMap.put(instance, typeKey);
                     }
                   }
                 }
                 index++;
                 total++;
                 progDialog.clsBar.setValue(index);
                 progDialog.totalBar.setValue(total);
               }
             }
             if (deletedInDBInstances.size() > 0) map.put(DELETE_IN_DB_KEY, deletedInDBInstances);
             if (newInstances.size() > 0) map.put(NEW_KEY, newInstances);
             if (changedInstances.size() > 0) map.put(CHANGED_KEY, changedInstances);
             if (localHasMoreIEInstances.size() > 0)
               map.put(LOCAL_HAS_MORE_IE_KEY, localHasMoreIEInstances);
             // Check the delete instances
             Map deleteMap = getLocalDeleteMap(fileAdaptor, syncClassList);
             if (deleteMap != null && deleteMap.size() > 0) {
               java.util.List deleteInstances = new ArrayList();
               List clearingIDs = new ArrayList();
               for (Iterator it = deleteMap.keySet().iterator(); it.hasNext(); ) {
                 Long dbID = (Long) it.next();
                 String className = (String) deleteMap.get(dbID);
                 dbCopy = dbAdaptor.fetchInstance(className, dbID);
                 if (dbCopy != null) {
                   deleteInstances.add(dbCopy);
                   typeMap.put(dbCopy, DELETE_KEY);
                 } else clearingIDs.add(dbID);
               }
               if (deleteInstances.size() > 0) map.put(DELETE_KEY, deleteInstances);
               fileAdaptor.clearDeleteRecord(clearingIDs);
             }
             progDialog.dispose();
           } catch (Exception e) {
             System.err.println("SynchronizationEngine.synchronize() 1: " + e);
             e.printStackTrace();
             JOptionPane.showMessageDialog(
                 progDialog,
                 "Error in synchronizing: \n" + e,
                 "Error in Synchronizing",
                 JOptionPane.ERROR_MESSAGE);
             // Have to set this first to behavior correctly since thread issue.
             isCancelled = true;
             progDialog.dispose();
           }
         }
       };
   t.start();
   progDialog.setVisible(true);
   return map;
 }