KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > team > internal > ccvs > ui > subscriber > SafeUpdateOperation


1 /*******************************************************************************
2  * Copyright (c) 2000, 2006 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.team.internal.ccvs.ui.subscriber;
12
13 import java.lang.reflect.InvocationTargetException JavaDoc;
14 import java.util.ArrayList JavaDoc;
15 import java.util.HashSet JavaDoc;
16 import java.util.List JavaDoc;
17 import java.util.Set JavaDoc;
18
19 import org.eclipse.compare.structuremergeviewer.IDiffElement;
20 import org.eclipse.core.resources.*;
21 import org.eclipse.core.runtime.IProgressMonitor;
22 import org.eclipse.jface.dialogs.MessageDialog;
23 import org.eclipse.osgi.util.NLS;
24 import org.eclipse.team.core.TeamException;
25 import org.eclipse.team.core.synchronize.FastSyncInfoFilter;
26 import org.eclipse.team.core.synchronize.SyncInfo;
27 import org.eclipse.team.core.synchronize.SyncInfoSet;
28 import org.eclipse.team.core.synchronize.FastSyncInfoFilter.AndSyncInfoFilter;
29 import org.eclipse.team.core.synchronize.FastSyncInfoFilter.OrSyncInfoFilter;
30 import org.eclipse.team.core.synchronize.FastSyncInfoFilter.SyncInfoDirectionFilter;
31 import org.eclipse.team.core.variants.IResourceVariant;
32 import org.eclipse.team.internal.ccvs.core.CVSException;
33 import org.eclipse.team.internal.ccvs.core.CVSProviderPlugin;
34 import org.eclipse.team.internal.ccvs.core.ICVSFile;
35 import org.eclipse.team.internal.ccvs.core.client.Command.LocalOption;
36 import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
37 import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo;
38 import org.eclipse.team.internal.ccvs.ui.*;
39 import org.eclipse.team.internal.ccvs.ui.operations.UpdateOnlyMergableOperation;
40 import org.eclipse.team.internal.ui.TeamUIPlugin;
41 import org.eclipse.team.ui.synchronize.ISynchronizePageConfiguration;
42
43 /**
44  * This update action will update all mergable resources first and then prompt the
45  * user to overwrite any resources that failed the safe update.
46  *
47  * Subclasses should determine how the update should handle conflicts by implementing
48  * the getOverwriteLocalChanges() method.
49  */

50 public abstract class SafeUpdateOperation extends CVSSubscriberOperation {
51
52     private boolean promptBeforeUpdate = false;
53     
54     private SyncInfoSet skipped = new SyncInfoSet();
55     
56     protected SafeUpdateOperation(ISynchronizePageConfiguration configuration, IDiffElement[] elements, boolean promptBeforeUpdate) {
57         super(configuration, elements);
58         this.promptBeforeUpdate = promptBeforeUpdate;
59     }
60     
61     /* (non-Javadoc)
62      * @see org.eclipse.team.ui.TeamOperation#shouldRun()
63      */

64     public boolean shouldRun() {
65         return promptIfNeeded();
66     }
67     
68     /* (non-Javadoc)
69      * @see org.eclipse.team.internal.ccvs.ui.subscriber.CVSSubscriberOperation#run(org.eclipse.core.runtime.IProgressMonitor)
70      */

71     public void run(IProgressMonitor monitor) throws InvocationTargetException JavaDoc, InterruptedException JavaDoc {
72         skipped.clear();
73         super.run(monitor);
74         try {
75             handleFailedUpdates(monitor);
76         } catch (TeamException e) {
77             throw new InvocationTargetException JavaDoc(e);
78         }
79     }
80     
81     /* (non-Javadoc)
82      * @see org.eclipse.team.internal.ccvs.ui.subscriber.CVSSubscriberAction#run(org.eclipse.team.ui.sync.SyncInfoSet, org.eclipse.core.runtime.IProgressMonitor)
83      */

84     public void runWithProjectRule(IProject project, SyncInfoSet syncSet, IProgressMonitor monitor) throws TeamException {
85         try {
86             monitor.beginTask(null, 100);
87             
88             // Remove the cases that are known to fail (adding them to skipped list)
89
removeKnownFailureCases(syncSet);
90             
91             // Run the update on the remaining nodes in the set
92
// The update will fail for conflicts that turn out to be non-automergable
93
safeUpdate(project, syncSet, Policy.subMonitorFor(monitor, 100));
94             
95             // Remove all failed conflicts from the original sync set
96
syncSet.rejectNodes(new FastSyncInfoFilter() {
97                 public boolean select(SyncInfo info) {
98                     return skipped.getSyncInfo(info.getLocal()) != null;
99                 }
100             });
101             
102             // Signal for the ones that were updated
103
updated(syncSet.getResources());
104         } finally {
105             monitor.done();
106         }
107     }
108
109     /**
110      * @param syncSet
111      * @return
112      */

113     private SyncInfoSet removeKnownFailureCases(SyncInfoSet syncSet) {
114         // First, remove any known failure cases
115
FastSyncInfoFilter failFilter = getKnownFailureCases();
116         SyncInfo[] willFail = syncSet.getNodes(failFilter);
117         syncSet.rejectNodes(failFilter);
118         for (int i = 0; i < willFail.length; i++) {
119             SyncInfo info = willFail[i];
120             skipped.add(info);
121         }
122         return syncSet;
123     }
124
125     private void handleFailedUpdates(IProgressMonitor monitor) throws TeamException {
126         // Handle conflicting files that can't be merged, ask the user what should be done.
127
if(! skipped.isEmpty()) {
128             if(getOverwriteLocalChanges()) {
129                 // Ask the user if a replace should be performed on the remaining nodes
130
if(promptForOverwrite(skipped)) {
131                     overwriteUpdate(skipped, monitor);
132                     if (!skipped.isEmpty()) {
133                         updated(skipped.getResources());
134                     }
135                 }
136             } else {
137                 // Warn the user that some nodes could not be updated. This can happen if there are
138
// files with conflicts that are not auto-mergeable.
139
warnAboutFailedResources(skipped);
140             }
141         }
142     }
143     
144     protected boolean getOverwriteLocalChanges(){
145         return false;
146     }
147
148     /**
149      * Perform a safe update on the resources in the provided set. Any included resources
150      * that cannot be updated safely wil be added to the skippedFiles list.
151      * @param syncSet the set containing the resources to be updated
152      * @param monitor
153      */

154     protected void safeUpdate(IProject project, SyncInfoSet syncSet, IProgressMonitor monitor) throws TeamException {
155         SyncInfo[] changed = syncSet.getSyncInfos();
156         if (changed.length == 0) return;
157         
158         // The list of sync resources to be updated using "cvs update"
159
List JavaDoc updateShallow = new ArrayList JavaDoc();
160         // A list of sync resource folders which need to be created locally
161
// (incoming addition or previously pruned)
162
Set JavaDoc parentCreationElements = new HashSet JavaDoc();
163         // A list of sync resources that are incoming deletions.
164
// We do these first to avoid case conflicts
165
List JavaDoc updateDeletions = new ArrayList JavaDoc();
166     
167         for (int i = 0; i < changed.length; i++) {
168             SyncInfo changedNode = changed[i];
169             
170             // Make sure that parent folders exist
171
SyncInfo parent = getParent(changedNode);
172             if (parent != null && isOutOfSync(parent)) {
173                 // We need to ensure that parents that are either incoming folder additions
174
// or previously pruned folders are recreated.
175
parentCreationElements.add(parent);
176             }
177             
178             IResource resource = changedNode.getLocal();
179             int kind = changedNode.getKind();
180             boolean willBeAttempted = false;
181             if (resource.getType() == IResource.FILE) {
182                 // Not all change types will require a "cvs update"
183
// Some can be deleted locally without performing an update
184
switch (kind & SyncInfo.DIRECTION_MASK) {
185                     case SyncInfo.INCOMING:
186                         switch (kind & SyncInfo.CHANGE_MASK) {
187                             case SyncInfo.DELETION:
188                                 // Incoming deletions can just be deleted instead of updated
189
updateDeletions.add(changedNode);
190                                 willBeAttempted = true;
191                                 break;
192                             default:
193                                 // add the file to the list of files to be updated
194
updateShallow.add(changedNode);
195                                 willBeAttempted = true;
196                                 break;
197                         }
198                         break;
199                     case SyncInfo.CONFLICTING:
200                         switch (kind & SyncInfo.CHANGE_MASK) {
201                             case SyncInfo.CHANGE:
202                                 // add the file to the list of files to be updated
203
updateShallow.add(changedNode);
204                                 willBeAttempted = true;
205                                 break;
206                         }
207                         break;
208                 }
209                 if (!willBeAttempted) {
210                     skipped.add(syncSet.getSyncInfo(resource));
211                 }
212             } else {
213                 // Special handling for folders to support shallow operations on files
214
// (i.e. folder operations are performed using the sync info already
215
// contained in the sync info.
216
if (isOutOfSync(changedNode)) {
217                     parentCreationElements.add(changedNode);
218                 }
219             }
220
221         }
222         try {
223             monitor.beginTask(null, 100);
224
225             if (updateDeletions.size() > 0) {
226                 runUpdateDeletions((SyncInfo[])updateDeletions.toArray(new SyncInfo[updateDeletions.size()]), Policy.subMonitorFor(monitor, 25));
227             }
228             if (parentCreationElements.size() > 0) {
229                 makeInSync((SyncInfo[]) parentCreationElements.toArray(new SyncInfo[parentCreationElements.size()]), Policy.subMonitorFor(monitor, 25));
230             }
231             if (updateShallow.size() > 0) {
232                 runSafeUpdate(project, (SyncInfo[])updateShallow.toArray(new SyncInfo[updateShallow.size()]), Policy.subMonitorFor(monitor, 50));
233             }
234         } finally {
235             monitor.done();
236         }
237         return;
238     }
239
240     /**
241      * Perform an overwrite (unsafe) update on the resources in the provided set.
242      * The passed sync set may containe resources from multiple projects and
243      * it cannot be assumed that any scheduling rule is held when this method
244      * is invoked.
245      * @param syncSet the set containing the resources to be updated
246      * @param monitor
247      */

248     protected abstract void overwriteUpdate(SyncInfoSet syncSet, IProgressMonitor monitor) throws TeamException;
249
250     /*
251      * Return a filter which selects the cases that we know ahead of time
252      * will fail on an update
253      */

254     protected FastSyncInfoFilter getKnownFailureCases() {
255         return new OrSyncInfoFilter(new FastSyncInfoFilter[] {
256             // Conflicting additions of files will fail
257
new AndSyncInfoFilter(new FastSyncInfoFilter[] {
258                 FastSyncInfoFilter.getDirectionAndChangeFilter(SyncInfo.CONFLICTING, SyncInfo.ADDITION),
259                 new FastSyncInfoFilter() {
260                     public boolean select(SyncInfo info) {
261                         return info.getLocal().getType() == IResource.FILE;
262                     }
263                 }
264             }),
265             // Conflicting changes of files will fail if the local is not managed
266
// or is an addition
267
new AndSyncInfoFilter(new FastSyncInfoFilter[] {
268                 FastSyncInfoFilter.getDirectionAndChangeFilter(SyncInfo.CONFLICTING, SyncInfo.CHANGE),
269                 new FastSyncInfoFilter() {
270                     public boolean select(SyncInfo info) {
271                         if (info.getLocal().getType() == IResource.FILE) {
272                             try {
273                                 ICVSFile cvsFile = CVSWorkspaceRoot.getCVSFileFor((IFile)info.getLocal());
274                                 byte[] syncBytes = cvsFile.getSyncBytes();
275                                 return (syncBytes == null || ResourceSyncInfo.isAddition(syncBytes));
276                             } catch (CVSException e) {
277                                 CVSUIPlugin.log(e);
278                                 // Fall though and try to update
279
}
280                         }
281                         return false;
282                     }
283                 }
284             }),
285             // Conflicting changes involving a deletion on one side will aways fail
286
new AndSyncInfoFilter(new FastSyncInfoFilter[] {
287                 FastSyncInfoFilter.getDirectionAndChangeFilter(SyncInfo.CONFLICTING, SyncInfo.CHANGE),
288                 new FastSyncInfoFilter() {
289                     public boolean select(SyncInfo info) {
290                         IResourceVariant remote = info.getRemote();
291                         IResourceVariant base = info.getBase();
292                         if (info.getLocal().exists()) {
293                             // local != base and no remote will fail
294
return (base != null && remote == null);
295                         } else {
296                             // no local and base != remote
297
return (base != null && remote != null && !base.equals(remote));
298                         }
299                     }
300                 }
301             }),
302             // Conflicts where the file type is binary will work but are not merged
303
// so they should be skipped
304
new AndSyncInfoFilter(new FastSyncInfoFilter[] {
305                 FastSyncInfoFilter.getDirectionAndChangeFilter(SyncInfo.CONFLICTING, SyncInfo.CHANGE),
306                 new FastSyncInfoFilter() {
307                     public boolean select(SyncInfo info) {
308                         IResource local = info.getLocal();
309                         if (local.getType() == IResource.FILE) {
310                             try {
311                                 ICVSFile file = CVSWorkspaceRoot.getCVSFileFor((IFile)local);
312                                 byte[] syncBytes = file.getSyncBytes();
313                                 if (syncBytes != null) {
314                                     return ResourceSyncInfo.isBinary(syncBytes);
315                                 }
316                             } catch (CVSException e) {
317                                 // There was an error obtaining or interpreting the sync bytes
318
// Log it and skip the file
319
CVSProviderPlugin.log(e);
320                                 return true;
321                             }
322                         }
323                         return false;
324                     }
325                 }
326             }),
327             // Outgoing changes may not fail but they are skipped as well
328
new SyncInfoDirectionFilter(SyncInfo.OUTGOING)
329         });
330     }
331     
332     /**
333      * Warn user that some files could not be updated.
334      * Note: This method is designed to be overridden by test cases.
335      */

336     protected void warnAboutFailedResources(final SyncInfoSet syncSet) {
337         TeamUIPlugin.getStandardDisplay().syncExec(new Runnable JavaDoc() {
338             public void run() {
339                 MessageDialog.openInformation(getShell(),
340                                 CVSUIMessages.SafeUpdateAction_warnFilesWithConflictsTitle,
341                                 CVSUIMessages.SafeUpdateAction_warnFilesWithConflictsDescription);
342             }
343         });
344     }
345     
346     /**
347      * This method is invoked for all resources in the sync set that are incoming deletions.
348      * It is done separately to allow deletions to be performed before additions that may
349      * be the same name with different letter case.
350      * @param nodes the SyncInfo nodes that are incoming deletions
351      * @param monitor
352      * @throws TeamException
353      */

354     protected abstract void runUpdateDeletions(SyncInfo[] nodes, IProgressMonitor monitor) throws TeamException;
355     
356     /**
357      * This method is invoked for all resources in the sync set that are incoming changes
358      * (but not deletions: @see runUpdateDeletions) or conflicting changes.
359      * This method should only update those conflicting resources that are automergable.
360      * @param project the project containing the nodes
361      * @param nodes the incoming or conflicting SyncInfo nodes
362      * @param monitor
363      * @throws TeamException
364      */

365     protected abstract void runSafeUpdate(IProject project, SyncInfo[] nodes, IProgressMonitor monitor) throws TeamException;
366     
367     protected void safeUpdate(IProject project, IResource[] resources, LocalOption[] localOptions, IProgressMonitor monitor) throws TeamException {
368         try {
369             UpdateOnlyMergableOperation operation = new UpdateOnlyMergableOperation(getPart(), project, resources, localOptions);
370             operation.run(monitor);
371             addSkippedFiles(operation.getSkippedFiles());
372         } catch (InvocationTargetException JavaDoc e) {
373             throw CVSException.wrapException(e);
374         } catch (InterruptedException JavaDoc e) {
375             Policy.cancelOperation();
376         }
377     }
378     
379     /**
380      * Notification of all resource that were updated (either safely or othrwise)
381      */

382     protected abstract void updated(IResource[] resources) throws TeamException;
383     
384     private void addSkippedFiles(IFile[] files) {
385         SyncInfoSet set = getSyncInfoSet();
386         for (int i = 0; i < files.length; i++) {
387             IFile file = files[i];
388             skipped.add(set.getSyncInfo(file));
389         }
390     }
391     
392     protected String JavaDoc getErrorTitle() {
393         return CVSUIMessages.UpdateAction_update;
394     }
395     
396     /* (non-Javadoc)
397      * @see org.eclipse.team.internal.ccvs.ui.subscriber.CVSSubscriberAction#getJobName(org.eclipse.team.ui.sync.SyncInfoSet)
398      */

399     protected String JavaDoc getJobName() {
400         SyncInfoSet syncSet = getSyncInfoSet();
401         return NLS.bind(CVSUIMessages.UpdateAction_jobName, new String JavaDoc[] { new Integer JavaDoc(syncSet.size()).toString() });
402     }
403
404     /**
405      * Confirm with the user what we are going to be doing. By default the update action doesn't
406      * prompt because the user has usually selected resources first. But in some cases, for example
407      * when performing a toolbar action, a confirmation prompt is nice.
408      * @param set the resources to be updated
409      * @return <code>true</code> if the update operation can continue, and <code>false</code>
410      * if the update has been cancelled by the user.
411      */

412     private boolean promptIfNeeded() {
413         final SyncInfoSet set = getSyncInfoSet();
414         final boolean[] result = new boolean[] {true};
415         if(getPromptBeforeUpdate()) {
416             TeamUIPlugin.getStandardDisplay().syncExec(new Runnable JavaDoc() {
417                 public void run() {
418                     String JavaDoc sizeString = Integer.toString(set.size());
419                     String JavaDoc message = set.size() > 1 ? NLS.bind(CVSUIMessages.UpdateAction_promptForUpdateSeveral, new String JavaDoc[] { sizeString }) : NLS.bind(CVSUIMessages.UpdateAction_promptForUpdateOne, new String JavaDoc[] { sizeString }); //
420
result[0] = MessageDialog.openQuestion(getShell(), NLS.bind(CVSUIMessages.UpdateAction_promptForUpdateTitle, new String JavaDoc[] { sizeString }), message);
421                 }
422             });
423         }
424         return result[0];
425     }
426     
427     public boolean getPromptBeforeUpdate() {
428         return promptBeforeUpdate;
429     }
430 }
431
Popular Tags