KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > team > core > mapping > provider > MergeContext


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.core.mapping.provider;
12
13 import java.io.*;
14 import java.util.*;
15
16 import org.eclipse.core.resources.*;
17 import org.eclipse.core.runtime.*;
18 import org.eclipse.core.runtime.jobs.ISchedulingRule;
19 import org.eclipse.core.runtime.jobs.MultiRule;
20 import org.eclipse.osgi.util.NLS;
21 import org.eclipse.team.core.diff.IDiff;
22 import org.eclipse.team.core.diff.IThreeWayDiff;
23 import org.eclipse.team.core.history.IFileRevision;
24 import org.eclipse.team.core.mapping.*;
25 import org.eclipse.team.internal.core.*;
26 import org.eclipse.team.internal.core.mapping.DelegatingStorageMerger;
27 import org.eclipse.team.internal.core.mapping.SyncInfoToDiffConverter;
28
29 /**
30  * Provides the context for an <code>IResourceMappingMerger</code>.
31  * It provides access to the ancestor and remote resource mapping contexts
32  * so that resource mapping mergers can attempt head-less auto-merges.
33  * The ancestor context is only required for merges while the remote
34  * is required for both merge and replace.
35  *
36  * @see IResourceMappingMerger
37  * @since 3.2
38  */

39 public abstract class MergeContext extends SynchronizationContext implements IMergeContext {
40
41     /**
42      * Create a merge context.
43      * @param type
44      */

45     protected MergeContext(ISynchronizationScopeManager manager, int type, IResourceDiffTree deltaTree) {
46         super(manager, type, deltaTree);
47     }
48     
49     /* (non-Javadoc)
50      * @see org.eclipse.team.core.mapping.IMergeContext#reject(org.eclipse.team.core.diff.IDiff[], org.eclipse.core.runtime.IProgressMonitor)
51      */

52     public void reject(final IDiff[] diffs, IProgressMonitor monitor) throws CoreException {
53         run(new IWorkspaceRunnable() {
54             public void run(IProgressMonitor monitor) throws CoreException {
55                 for (int i = 0; i < diffs.length; i++) {
56                     IDiff node = diffs[i];
57                     reject(node, monitor);
58                 }
59             }
60         }, getMergeRule(diffs), IResource.NONE, monitor);
61     }
62     
63     /* (non-Javadoc)
64      * @see org.eclipse.team.core.mapping.IMergeContext#markAsMerged(org.eclipse.team.core.diff.IDiffNode[], boolean, org.eclipse.core.runtime.IProgressMonitor)
65      */

66     public void markAsMerged(final IDiff[] nodes, final boolean inSyncHint, IProgressMonitor monitor) throws CoreException {
67         run(new IWorkspaceRunnable() {
68             public void run(IProgressMonitor monitor) throws CoreException {
69                 for (int i = 0; i < nodes.length; i++) {
70                     IDiff node = nodes[i];
71                     markAsMerged(node, inSyncHint, monitor);
72                 }
73             }
74         }, getMergeRule(nodes), IResource.NONE, monitor);
75     }
76     
77     /* (non-Javadoc)
78      * @see org.eclipse.team.ui.mapping.IMergeContext#merge(org.eclipse.team.core.delta.ISyncDelta[], boolean, org.eclipse.core.runtime.IProgressMonitor)
79      */

80     public IStatus merge(final IDiff[] deltas, final boolean force, IProgressMonitor monitor) throws CoreException {
81         final List failedFiles = new ArrayList();
82         run(new IWorkspaceRunnable() {
83             public void run(IProgressMonitor monitor) throws CoreException {
84                 try {
85                     monitor.beginTask(null, deltas.length * 100);
86                     for (int i = 0; i < deltas.length; i++) {
87                         IDiff delta = deltas[i];
88                         IStatus s = merge(delta, force, Policy.subMonitorFor(monitor, 100));
89                         if (!s.isOK()) {
90                             if (s.getCode() == IMergeStatus.CONFLICTS) {
91                                 failedFiles.addAll(Arrays.asList(((IMergeStatus)s).getConflictingFiles()));
92                             } else {
93                                 throw new CoreException(s);
94                             }
95                         }
96                     }
97                 } finally {
98                     monitor.done();
99                 }
100             }
101         }, getMergeRule(deltas), IWorkspace.AVOID_UPDATE, monitor);
102         if (failedFiles.isEmpty()) {
103             return Status.OK_STATUS;
104         } else {
105             return new MergeStatus(TeamPlugin.ID, Messages.MergeContext_0, (IFile[]) failedFiles.toArray(new IFile[failedFiles.size()]));
106         }
107     }
108     
109     /* (non-Javadoc)
110      * @see org.eclipse.team.core.mapping.IMergeContext#merge(org.eclipse.team.core.diff.IDiffNode, boolean, org.eclipse.core.runtime.IProgressMonitor)
111      */

112     public IStatus merge(IDiff diff, boolean ignoreLocalChanges, IProgressMonitor monitor) throws CoreException {
113         Policy.checkCanceled(monitor);
114         IResource resource = getDiffTree().getResource(diff);
115         if (resource.getType() != IResource.FILE) {
116             if (diff instanceof IThreeWayDiff) {
117                 IThreeWayDiff twd = (IThreeWayDiff) diff;
118                 if ((ignoreLocalChanges || getMergeType() == TWO_WAY)
119                         && resource.getType() == IResource.FOLDER
120                         && twd.getKind() == IDiff.ADD
121                         && twd.getDirection() == IThreeWayDiff.OUTGOING
122                         && ((IFolder)resource).members().length == 0) {
123                     // Delete the local folder addition
124
((IFolder)resource).delete(false, monitor);
125                 } else if (resource.getType() == IResource.FOLDER
126                         && !resource.exists()
127                         && twd.getKind() == IDiff.ADD
128                         && twd.getDirection() == IThreeWayDiff.INCOMING) {
129                     ensureParentsExist(resource, monitor);
130                     ((IFolder)resource).create(false, true, monitor);
131                     makeInSync(diff, monitor);
132                 }
133             }
134             return Status.OK_STATUS;
135         }
136         if (diff instanceof IThreeWayDiff && !ignoreLocalChanges && getMergeType() == THREE_WAY) {
137             IThreeWayDiff twDelta = (IThreeWayDiff) diff;
138             int direction = twDelta.getDirection();
139             if (direction == IThreeWayDiff.OUTGOING) {
140                 // There's nothing to do so return OK
141
return Status.OK_STATUS;
142             }
143             if (direction == IThreeWayDiff.INCOMING) {
144                 // Just copy the stream since there are no conflicts
145
performReplace(diff, monitor);
146                 return Status.OK_STATUS;
147             }
148             // direction == SyncInfo.CONFLICTING
149
int type = twDelta.getKind();
150             if (type == IDiff.REMOVE) {
151                 makeInSync(diff, monitor);
152                 return Status.OK_STATUS;
153             }
154             // type == SyncInfo.CHANGE
155
IResourceDiff remoteChange = (IResourceDiff)twDelta.getRemoteChange();
156             IFileRevision remote = null;
157             if (remoteChange != null) {
158                 remote = remoteChange.getAfterState();
159             }
160             if (remote == null || !getLocalFile(diff).exists()) {
161                 // Nothing we can do so return a conflict status
162
// TODO: Should we handle the case where the local and remote have the same contents for a conflicting addition?
163
return new MergeStatus(TeamPlugin.ID, NLS.bind(Messages.MergeContext_1, new String JavaDoc[] { diff.getPath().toString() }), new IFile[] { getLocalFile(diff) });
164             }
165             // We have a conflict, a local, base and remote so we can do
166
// a three-way merge
167
return performThreeWayMerge(twDelta, monitor);
168         } else {
169             performReplace(diff, monitor);
170             return Status.OK_STATUS;
171         }
172         
173     }
174
175     /**
176      * Perform a three-way merge on the given three-way diff that contains a content conflict.
177      * By default, this method makes use of {@link IStorageMerger} instances registered
178      * with the <code>storageMergers</code> extension point. Note that the ancestor
179      * of the given diff may be missing. Some {@link IStorageMerger} instances
180      * can still merge without an ancestor so we need to consult the
181      * appropriate merger to find out.
182      * @param diff the diff
183      * @param monitor a progress monitor
184      * @return a status indicating the results of the merge
185      */

186     protected IStatus performThreeWayMerge(final IThreeWayDiff diff, IProgressMonitor monitor) throws CoreException {
187         final IStatus[] result = new IStatus[] { Status.OK_STATUS };
188         run(new IWorkspaceRunnable() {
189             public void run(IProgressMonitor monitor) throws CoreException {
190                 monitor.beginTask(null, 100);
191                 IResourceDiff localDiff = (IResourceDiff)diff.getLocalChange();
192                 IResourceDiff remoteDiff = (IResourceDiff)diff.getRemoteChange();
193                 IStorageMerger merger = (IStorageMerger)getAdapter(IStorageMerger.class);
194                 if (merger == null)
195                     merger = DelegatingStorageMerger.getInstance();
196                 IFile file = (IFile)localDiff.getResource();
197                 monitor.subTask(NLS.bind(Messages.MergeContext_5, file.getFullPath().toString()));
198                 String JavaDoc osEncoding = file.getCharset();
199                 IFileRevision ancestorState = localDiff.getBeforeState();
200                 IFileRevision remoteState = remoteDiff.getAfterState();
201                 IStorage ancestorStorage;
202                 if (ancestorState != null)
203                     ancestorStorage = ancestorState.getStorage(Policy.subMonitorFor(monitor, 30));
204                 else
205                     ancestorStorage = null;
206                 IStorage remoteStorage = remoteState.getStorage(Policy.subMonitorFor(monitor, 30));
207                 OutputStream os = getTempOutputStream(file);
208                 try {
209                     IStatus status = merger.merge(os, osEncoding, ancestorStorage, file, remoteStorage, Policy.subMonitorFor(monitor, 30));
210                     if (status.isOK()) {
211                         file.setContents(getTempInputStream(file, os), false, true, Policy.subMonitorFor(monitor, 5));
212                         markAsMerged(diff, false, Policy.subMonitorFor(monitor, 5));
213                     } else {
214                         status = new MergeStatus(status.getPlugin(), status.getMessage(), new IFile[]{file});
215                     }
216                     result[0] = status;
217                 } finally {
218                     disposeTempOutputStream(file, os);
219                 }
220                 monitor.done();
221             }
222         }, getMergeRule(diff), IWorkspace.AVOID_UPDATE, monitor);
223         return result[0];
224     }
225
226     private void disposeTempOutputStream(IFile file, OutputStream output) {
227         if (output instanceof ByteArrayOutputStream)
228             return;
229         // We created a temporary file so we need to clean it up
230
try {
231             // First make sure the output stream is closed
232
// so that file deletion will not fail because of that.
233
if (output != null)
234                 output.close();
235         } catch (IOException e) {
236             // Ignore
237
}
238         File tmpFile = getTempFile(file);
239         if (tmpFile.exists())
240             tmpFile.delete();
241     }
242     
243     private OutputStream getTempOutputStream(IFile file) throws CoreException {
244         File tmpFile = getTempFile(file);
245         if (tmpFile.exists())
246             tmpFile.delete();
247         File parent = tmpFile.getParentFile();
248         if (!parent.exists())
249             parent.mkdirs();
250         try {
251             return new BufferedOutputStream(new FileOutputStream(tmpFile));
252         } catch (FileNotFoundException e) {
253             TeamPlugin.log(IStatus.ERROR, NLS.bind("Could not open temporary file {0} for writing: {1}", new String JavaDoc[] { tmpFile.getAbsolutePath(), e.getMessage() }), e); //$NON-NLS-1$
254
return new ByteArrayOutputStream();
255         }
256     }
257     
258     private InputStream getTempInputStream(IFile file, OutputStream output) throws CoreException {
259         if (output instanceof ByteArrayOutputStream) {
260             ByteArrayOutputStream baos = (ByteArrayOutputStream) output;
261             return new ByteArrayInputStream(baos.toByteArray());
262         }
263         // We created a temporary file so we need to open an input stream on it
264
try {
265             // First make sure the output stream is closed
266
if (output != null)
267                 output.close();
268         } catch (IOException e) {
269             // Ignore
270
}
271         File tmpFile = getTempFile(file);
272         try {
273             return new BufferedInputStream(new FileInputStream(tmpFile));
274         } catch (FileNotFoundException e) {
275             throw new CoreException(new Status(IStatus.ERROR, TeamPlugin.ID, IMergeStatus.INTERNAL_ERROR, NLS.bind(Messages.MergeContext_4, new String JavaDoc[] { tmpFile.getAbsolutePath(), e.getMessage() }), e));
276         }
277     }
278     
279     private File getTempFile(IFile file) {
280         return TeamPlugin.getPlugin().getStateLocation().append(".tmp").append(file.getName() + ".tmp").toFile(); //$NON-NLS-1$ //$NON-NLS-2$
281
}
282     
283     private IFile getLocalFile(IDiff delta) {
284         return ResourcesPlugin.getWorkspace().getRoot().getFile(delta.getPath());
285     }
286
287     /**
288      * Make the local state of the resource associated with the given diff match
289      * that of the remote. This method is invoked by the
290      * {@link #merge(IDiff, boolean, IProgressMonitor)} method. By default, it
291      * either overwrites the local contexts with the remote contents if both
292      * exist, deletes the local if the rmeote does not exists or addes the local
293      * if the local doesn't exist but the remote does. It then calls
294      * {@link #makeInSync(IDiff, IProgressMonitor)} to give subclasses a change
295      * to make the file associated with the diff in-sync.
296      *
297      * @param diff
298      * the diff whose local is to be replaced
299      * @param monitor
300      * a progrss monitor
301      * @throws CoreException
302      */

303     protected void performReplace(final IDiff diff, IProgressMonitor monitor) throws CoreException {
304         IResourceDiff d;
305         IFile file = getLocalFile(diff);
306         IFileRevision remote = null;
307         if (diff instanceof IResourceDiff) {
308             d = (IResourceDiff) diff;
309             if (d != null)
310                 remote = d.getAfterState();
311         } else {
312             d = (IResourceDiff)((IThreeWayDiff)diff).getRemoteChange();
313             if (d != null)
314                 remote = d.getAfterState();
315         }
316         if (d == null) {
317             d = (IResourceDiff)((IThreeWayDiff)diff).getLocalChange();
318             if (d != null)
319                 remote = d.getBeforeState();
320         }
321         
322         // Only perform the replace if a local or remote change was found
323
if (d != null) {
324             performReplace(diff, file, remote, monitor);
325         }
326     }
327     
328     /**
329      * Method that is invoked from
330      * {@link #performReplace(IDiff, IProgressMonitor)} after the local has been
331      * changed to match the remote. Subclasses may override
332      * {@link #performReplace(IDiff, IProgressMonitor)} or this method in order
333      * to properly reconcile the synchronization state. This method is also
334      * invoked from {@link #merge(IDiff, boolean, IProgressMonitor)} if deletion
335      * conflicts are encountered. It can also be invoked from that same method if
336      * a folder is created due to an incoming folder addition.
337      *
338      * @param diff
339      * the diff whose local is now in-sync
340      * @param monitor
341      * a progress monitor
342      * @throws CoreException
343      */

344     protected abstract void makeInSync(IDiff diff, IProgressMonitor monitor) throws CoreException;
345
346     private void performReplace(final IDiff diff, final IFile file, final IFileRevision remote, IProgressMonitor monitor) throws CoreException {
347         run(new IWorkspaceRunnable() {
348             public void run(IProgressMonitor monitor) throws CoreException {
349                 try {
350                     monitor.beginTask(null, 100);
351                     monitor.subTask(NLS.bind(Messages.MergeContext_6, file.getFullPath().toString()));
352                     if ((remote == null || !remote.exists()) && file.exists()) {
353                         file.delete(false, true, Policy.subMonitorFor(monitor, 95));
354                     } else if (remote != null) {
355                         ensureParentsExist(file, monitor);
356                         InputStream stream = remote.getStorage(monitor).getContents();
357                         stream = new BufferedInputStream(stream);
358                         try {
359                             if (file.exists()) {
360                                 file.setContents(stream, false, true, Policy.subMonitorFor(monitor, 95));
361                             } else {
362                                 file.create(stream, false, Policy.subMonitorFor(monitor, 95));
363                             }
364                         } finally {
365                             try {
366                                 stream.close();
367                             } catch (IOException e) {
368                                 // Ignore
369
}
370                         }
371                     }
372                     // Performing a replace should leave the file in-sync
373
makeInSync(diff, Policy.subMonitorFor(monitor, 5));
374                 } finally {
375                     monitor.done();
376                 }
377             }
378         }, getMergeRule(diff), IWorkspace.AVOID_UPDATE, monitor);
379     }
380
381     /**
382      * Ensure that the parent folders of the given resource exist.
383      * This method is invoked from {@link #performReplace(IDiff, IProgressMonitor)}
384      * for files that are being merged that do not exist locally.
385      * By default, this method creates the parents using
386      * {@link IFolder#create(boolean, boolean, IProgressMonitor)}.
387      * Subclasses may override.
388      * @param resource a resource
389      * @param monitor a progress monitor
390      * @throws CoreException if an error occurs
391      */

392     protected void ensureParentsExist(IResource resource, IProgressMonitor monitor) throws CoreException {
393         IContainer parent = resource.getParent();
394         if (parent.getType() != IResource.FOLDER) {
395             // this method will only create folders
396
return;
397         }
398         if (!parent.exists()) {
399             ensureParentsExist(parent, monitor);
400             ((IFolder)parent).create(false, true, monitor);
401         }
402     }
403     
404     /**
405      * Default implementation of <code>run</code> that invokes the
406      * corresponding <code>run</code> on {@link org.eclipse.core.resources.IWorkspace}.
407      * @see org.eclipse.team.core.mapping.IMergeContext#run(org.eclipse.core.resources.IWorkspaceRunnable, org.eclipse.core.runtime.jobs.ISchedulingRule, int, org.eclipse.core.runtime.IProgressMonitor)
408      */

409     public void run(IWorkspaceRunnable runnable, ISchedulingRule rule, int flags, IProgressMonitor monitor) throws CoreException {
410         ResourcesPlugin.getWorkspace().run(runnable, rule, flags, monitor);
411     }
412     
413     /**
414      * Default implementation that returns the resource itself.
415      * Subclass should override to provide the appropriate rule.
416      * @see org.eclipse.team.core.mapping.IMergeContext#getMergeRule(IDiff)
417      */

418     public ISchedulingRule getMergeRule(IDiff diff) {
419         IResource resource = getDiffTree().getResource(diff);
420         IResourceRuleFactory ruleFactory = ResourcesPlugin.getWorkspace().getRuleFactory();
421         ISchedulingRule rule;
422         if (!resource.exists()) {
423             rule = ruleFactory.createRule(resource);
424         } else if (SyncInfoToDiffConverter.getRemote(diff) == null){
425             rule = ruleFactory.deleteRule(resource);
426         } else {
427             rule = ruleFactory.modifyRule(resource);
428         }
429         return rule;
430     }
431     
432     /* (non-Javadoc)
433      * @see org.eclipse.team.core.mapping.IMergeContext#getMergeRule(org.eclipse.team.core.diff.IDiff[])
434      */

435     public ISchedulingRule getMergeRule(IDiff[] deltas) {
436         ISchedulingRule result = null;
437         for (int i = 0; i < deltas.length; i++) {
438             IDiff node = deltas[i];
439             ISchedulingRule rule = getMergeRule(node);
440             if (result == null) {
441                 result = rule;
442             } else {
443                 result = MultiRule.combine(result, rule);
444             }
445         }
446         return result;
447     }
448     
449     /* (non-Javadoc)
450      * @see org.eclipse.team.core.mapping.IMergeContext#getMergeType()
451      */

452     public int getMergeType() {
453         return getType();
454     }
455     
456     public Object JavaDoc getAdapter(Class JavaDoc adapter) {
457         if (adapter == IStorageMerger.class) {
458             return DelegatingStorageMerger.getInstance();
459         }
460         return super.getAdapter(adapter);
461     }
462 }
463
Popular Tags