Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 6dc403b0e7afa59e87a1984958fb15356da5bdcc (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
/*******************************************************************************
 * Copyright (c) 2006, 2018 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 * IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.team.examples.model.mapping;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.mapping.ResourceMapping;
import org.eclipse.core.resources.mapping.ResourceTraversal;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.team.core.diff.FastDiffFilter;
import org.eclipse.team.core.diff.IDiff;
import org.eclipse.team.core.diff.IThreeWayDiff;
import org.eclipse.team.core.history.IFileRevision;
import org.eclipse.team.core.mapping.IMergeContext;
import org.eclipse.team.core.mapping.IResourceDiff;
import org.eclipse.team.core.mapping.ResourceMappingMerger;
import org.eclipse.team.core.mapping.provider.MergeStatus;
import org.eclipse.team.core.mapping.provider.ResourceDiffTree;
import org.eclipse.team.core.mapping.provider.SynchronizationContext;
import org.eclipse.team.examples.filesystem.FileSystemPlugin;
import org.eclipse.team.examples.model.ModelObject;
import org.eclipse.team.examples.model.ModelObjectDefinitionFile;
import org.eclipse.team.examples.model.ModelProject;

/**
 * A resource mapping merger for our example model
 */
public class ModelMerger extends ResourceMappingMerger {

	private final org.eclipse.team.examples.model.mapping.ExampleModelProvider provider;

	public ModelMerger(org.eclipse.team.examples.model.mapping.ExampleModelProvider provider) {
		this.provider = provider;
	}

	@Override
	protected org.eclipse.core.resources.mapping.ModelProvider getModelProvider() {
		return provider;
	}

	@Override
	public IStatus merge(IMergeContext mergeContext, IProgressMonitor monitor) throws CoreException {
		try {
			IStatus status;
			// Only override the merge for three-way synchronizations
			if (mergeContext.getType() == SynchronizationContext.THREE_WAY) {
				monitor.beginTask("Merging model elements", 100);
				status = mergeModelElements(mergeContext, SubMonitor.convert(monitor, 50));
				// Stop the merge if there was a failure
				if (!status.isOK())
					return status;
				// We need to wait for any background processing to complete for the context
				// so the diff tree will be up-to-date when we delegate the rest of the merge
				// to the superclass
				try {
					Job.getJobManager().join(mergeContext, SubMonitor.convert(monitor, 50));
				} catch (InterruptedException e) {
					// Ignore
				}
				// Delegate the rest of the merge to the superclass
				status = super.merge(mergeContext, monitor);
			} else {
				status = super.merge(mergeContext, monitor);
			}
			return status;
		} finally {
			monitor.done();
		}
	}

	/*
	 * Merge all the model element changes in the context
	 */
	private IStatus mergeModelElements(IMergeContext mergeContext, IProgressMonitor monitor) throws CoreException {
		try {
			IDiff[] modeDiffs = getModDiffs(mergeContext);
			List<IDiff> failures = new ArrayList<>();
			monitor.beginTask(null, 100 * modeDiffs.length);
			for (IDiff diff : modeDiffs) {
				if (!mergeModelElement(mergeContext, diff, SubMonitor.convert(monitor, 100))) {
					failures.add(diff);
				}
			}
			if (failures.size() > 0) {
				return new MergeStatus(FileSystemPlugin.ID, "Several objects could not be merged", getMappings(failures));
			}
			return Status.OK_STATUS;
		} finally {
			monitor.done();
		}
	}

	private ResourceMapping[] getMappings(List failures) {
		List<ResourceMapping> mappings = new ArrayList<>();
		for (Iterator iter = failures.iterator(); iter.hasNext();) {
			IDiff diff = (IDiff) iter.next();
			IResource resource = ResourceDiffTree.getResourceFor(diff);
			ModelObjectDefinitionFile file = (ModelObjectDefinitionFile)ModelObject.create(resource);
			mappings.add(file.getAdapter(ResourceMapping.class));
		}
		return mappings.toArray(new ResourceMapping[mappings.size()]);
	}

	/*
	 * Return all the diffs for MOD files.
	 */
	private IDiff[] getModDiffs(IMergeContext mergeContext) {
		final List<IDiff> result = new ArrayList<>();
		mergeContext.getDiffTree().accept(getModelProjectTraversals(mergeContext), diff -> {
			IResource resource = ResourceDiffTree.getResourceFor(diff);
			if (ModelObjectDefinitionFile.isModFile(resource)) {
				result.add(diff);
			}
			return true;
		});
		return result.toArray(new IDiff[result.size()]);
	}

	/*
	 * Return a traversal that covers all the model projects in the scope of the merge.
	 */
	private ResourceTraversal[] getModelProjectTraversals(IMergeContext mergeContext) {
		IProject[] scopeProjects = mergeContext.getScope().getProjects();
		List<IResource> modelProjects = new ArrayList<>();
		for (IProject project : scopeProjects) {
			try {
				if (ModelProject.isModProject(project)) {
					modelProjects.add(project);
				}
			} catch (CoreException e) {
				FileSystemPlugin.log(e);
			}
		}
		if (modelProjects.isEmpty())
			return new ResourceTraversal[0];
		return new ResourceTraversal[] {
				new ResourceTraversal(modelProjects.toArray(new IResource[modelProjects.size()]),
						IResource.DEPTH_INFINITE, IResource.NONE)
		};
	}

	/*
	 * Merge the model definition file and all the element files it contains.
	 */
	private boolean mergeModelElement(IMergeContext mergeContext, IDiff diff, IProgressMonitor monitor) throws CoreException {
		if (diff instanceof IThreeWayDiff) {
			IThreeWayDiff twd = (IThreeWayDiff) diff;
			if (twd.getDirection() == IThreeWayDiff.INCOMING
					|| twd.getDirection() == IThreeWayDiff.CONFLICTING) {
				IResource resource = ResourceDiffTree.getResourceFor(diff);

				// First, check if a change conflicts with a deletion
				if (twd.getDirection() == IThreeWayDiff.CONFLICTING) {
					if (!resource.exists())
						return false;
					if (((IResourceDiff)twd.getRemoteChange()).getAfterState() == null)
						return false;
				}

				// First determine the element files and element file changes
				IResourceDiff remoteChange = (IResourceDiff)twd.getRemoteChange();
				IResource[] localElements = getReferencedResources(resource);
				IResource[] baseElements = getReferencedResources(resource.getProject().getName(), remoteChange.getBeforeState(), monitor);
				IResource[] remoteElements = getReferencedResources(resource.getProject().getName(), remoteChange.getAfterState(), monitor);
				IResource[] addedElements = getAddedElements(baseElements, remoteElements);
				// Trick: The removed elements can be obtained by reversing the base and remote and looking for added
				IResource[] removedElements = getAddedElements(remoteElements, baseElements);

				// Check to see if any removed elements have changed locally
				if (hasOutgoingChanges(mergeContext, removedElements)) {
					return false;
				}

				// Now try to merge all the element files involved
				Set<IResource> elementFiles = new HashSet<>();
				elementFiles.addAll(Arrays.asList(baseElements));
				elementFiles.addAll(Arrays.asList(localElements));
				elementFiles.addAll(Arrays.asList(remoteElements));
				if (!mergeElementFiles(mergeContext, elementFiles.toArray(new IResource[elementFiles.size()]), monitor)) {
					return false;
				}

				// Finally, merge the model definition
				if (!resource.exists()) {
					// This is a new model definition so just merge it
					IStatus status = mergeContext.merge(diff, false, monitor);
					if (!status.isOK())
						return false;
				} else {
					// Update the contents of the model definition file
					ModelObjectDefinitionFile file = (ModelObjectDefinitionFile)ModelObject.create(resource);
					elementFiles = new HashSet<>();
					elementFiles.addAll(Arrays.asList(localElements));
					elementFiles.addAll(Arrays.asList(addedElements));
					elementFiles.removeAll(Arrays.asList(removedElements));
					file.setElements(elementFiles.toArray(new IResource[elementFiles.size()]));
					// Let the merge context know we handled the file
					mergeContext.markAsMerged(diff, false, monitor);
				}
			}
		}
		return true;
	}

	private boolean mergeElementFiles(IMergeContext mergeContext, IResource[] resources, IProgressMonitor monitor) throws CoreException {
		IDiff[] diffs = getDiffs(mergeContext, resources);
		IStatus status = mergeContext.merge(diffs, false, monitor);
		return status.isOK();
	}

	private IDiff[] getDiffs(IMergeContext mergeContext, IResource[] resources) {
		Set<IDiff> diffSet = new HashSet<>();
		for (IResource resource : resources) {
			IDiff[] diffs = mergeContext.getDiffTree().getDiffs(resource, IResource.DEPTH_ZERO);
			diffSet.addAll(Arrays.asList(diffs));
		}
		return diffSet.toArray(new IDiff[diffSet.size()]);
	}

	private boolean hasOutgoingChanges(IMergeContext mergeContext, IResource[] removedElements) {
		FastDiffFilter fastDiffFilter = new FastDiffFilter() {
			@Override
			public boolean select(IDiff diff) {
				if (diff instanceof IThreeWayDiff) {
					IThreeWayDiff twd = (IThreeWayDiff) diff;
					return twd.getDirection() == IThreeWayDiff.OUTGOING || twd.getDirection() == IThreeWayDiff.CONFLICTING;
				}
				return false;
			}
		};
		for (IResource resource : removedElements) {
			if  (mergeContext.getDiffTree().hasMatchingDiffs(resource.getFullPath(), fastDiffFilter))
				return true;
		}
		return false;
	}

	private IResource[] getAddedElements(IResource[] baseElements, IResource[] remoteElements) {
		List<IResource> result = new ArrayList<>();
		Set<IResource> base = new HashSet<>();
		for (IResource resource : baseElements) {
			base.add(resource);
		}
		for (IResource resource : remoteElements) {
			if (!base.contains(resource))
				result.add(resource);
		}
		return result.toArray(new IResource[result.size()]);
	}

	private IResource[] getReferencedResources(IResource resource) throws CoreException {
		if (resource instanceof IFile && resource.exists()) {
			return ModelObjectDefinitionFile.getReferencedResources(resource.getProject().getName(), (IFile) resource);
		}
		return new IResource[0];
	}

	private IResource[] getReferencedResources(String projectName, IFileRevision revision, IProgressMonitor monitor) throws CoreException {
		if (revision != null) {
			return ModelObjectDefinitionFile.getReferencedResources(projectName, revision.getStorage(monitor));
		}
		return new IResource[0];
	}

}

Back to the top