Skip to main content
summaryrefslogtreecommitdiffstats
blob: bd5589400a9323de7f3f464fe31f19aa3f067280 (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
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
/*******************************************************************************
 * Copyright (c) 2014, 2015 Obeo and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Obeo - initial API and implementation
 *     Philip Langer - bug 469355, bug 462884, refactorings
 *******************************************************************************/
package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.actions;

import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Lists.newArrayList;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.containsConflictOfTypes;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.fromSide;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.hasConflict;

import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import org.eclipse.emf.common.util.BasicMonitor;
import org.eclipse.emf.common.util.Monitor;
import org.eclipse.emf.compare.Comparison;
import org.eclipse.emf.compare.ConflictKind;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.DifferenceSource;
import org.eclipse.emf.compare.DifferenceState;
import org.eclipse.emf.compare.domain.IMergeRunnable;
import org.eclipse.emf.compare.internal.domain.IMergeAllNonConflictingRunnable;
import org.eclipse.emf.compare.internal.merge.MergeDependenciesUtil;
import org.eclipse.emf.compare.internal.merge.MergeMode;
import org.eclipse.emf.compare.internal.merge.MergeOperation;
import org.eclipse.emf.compare.internal.utils.ComparisonUtil;
import org.eclipse.emf.compare.internal.utils.Graph;
import org.eclipse.emf.compare.internal.utils.PruningIterator;
import org.eclipse.emf.compare.merge.BatchMerger;
import org.eclipse.emf.compare.merge.IBatchMerger;
import org.eclipse.emf.compare.merge.IMerger;
import org.eclipse.emf.compare.merge.IMerger.Registry;
import org.eclipse.emf.compare.merge.IMerger2;

/**
 * Implements the "merge non-conflicting" and "merge all non-conflicting" action.
 * 
 * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
 */
public class MergeNonConflictingRunnable extends AbstractMergeRunnable implements IMergeAllNonConflictingRunnable, IMergeRunnable {
	/**
	 * Default constructor.
	 * 
	 * @param isLeftEditable
	 *            Whether the left side of the comparison we're operating on is editable.
	 * @param isRightEditable
	 *            Whether the right side of the comparison we're operating on is editable.
	 * @param mergeMode
	 *            Merge mode for this operation.
	 */
	public MergeNonConflictingRunnable(boolean isLeftEditable, boolean isRightEditable, MergeMode mergeMode) {
		super(isLeftEditable, isRightEditable, mergeMode);
	}

	/**
	 * {@inheritDoc}
	 */
	public Iterable<Diff> merge(Comparison comparison, boolean leftToRight, Registry mergerRegistry) {
		checkState(getMergeMode().isLeftToRight(isLeftEditable(), isRightEditable()) == leftToRight);
		return doMergeNonConflicting(comparison.getDifferences(), comparison, leftToRight, mergerRegistry);
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * Differences that are conflicting or that depend on conflicting differences will be left out.
	 * Non-conflicting differences that are implied or required by the given differences will be merged, also
	 * if they are not explicitly included in the given list of {@code differences}.
	 * </p>
	 */
	@SuppressWarnings("unchecked")
	public void merge(List<? extends Diff> differences, boolean leftToRight, Registry mergerRegistry) {
		checkState(getMergeMode().isLeftToRight(isLeftEditable(), isRightEditable()) == leftToRight);
		checkState(!differences.isEmpty() && ComparisonUtil.getComparison(differences.get(0)) != null);
		final Comparison comparison = ComparisonUtil.getComparison(differences.get(0));
		doMergeNonConflicting((Collection<Diff>)differences, comparison, leftToRight, mergerRegistry);
	}

	/**
	 * Performs the merge of the non-conflicting differences in the given {@code differences}.
	 * 
	 * @param differences
	 *            The differences to be merged.
	 * @param comparison
	 *            The comparison containing the differences to decide on whether conflicts are in play or not
	 *            and to determine whether this is a three- or two-way comparison.
	 * @param leftToRight
	 *            The direction in which {@code differences} should be merged.
	 * @param mergerRegistry
	 *            The registry of mergers.
	 * @return an iterable over the differences that have actually been merged by this operation.
	 */
	private Iterable<Diff> doMergeNonConflicting(Collection<Diff> differences, Comparison comparison,
			boolean leftToRight, Registry mergerRegistry) {
		final Iterable<Diff> affectedChanges;
		if (hasRealConflict(comparison)) {
			// This is a 3-way comparison, pre-merge what can be.
			affectedChanges = mergeWithConflicts(differences, leftToRight, mergerRegistry);
		} else if (comparison.isThreeWay()) {
			// This is a 3-way comparison without conflicts
			affectedChanges = mergeThreeWayWithoutConflicts(differences, leftToRight, mergerRegistry);
		} else {
			// This is a 2-way comparison
			affectedChanges = mergeTwoWay(differences, leftToRight, mergerRegistry);
		}
		return affectedChanges;
	}

	/**
	 * Handles the merge of all non-conflicting differences in case of a three-way comparison without
	 * conflicts.
	 * 
	 * @param differences
	 *            The differences to be merged.
	 * @param leftToRight
	 *            The direction in which {@code differences} should be merged.
	 * @param mergerRegistry
	 *            The registry of mergers.
	 * @return an iterable over the differences that have actually been merged by this operation.
	 */
	private Iterable<Diff> mergeThreeWayWithoutConflicts(Collection<Diff> differences, boolean leftToRight,
			Registry mergerRegistry) {
		final List<Diff> affectedDiffs;
		final IBatchMerger merger = new BatchMerger(mergerRegistry);
		if (getMergeMode() == MergeMode.LEFT_TO_RIGHT) {
			affectedDiffs = Lists
					.newArrayList(Iterables.filter(differences, fromSide(DifferenceSource.LEFT)));
			merger.copyAllLeftToRight(affectedDiffs, new BasicMonitor());
			addOrUpdateMergeData(affectedDiffs, getMergeMode());
		} else if (getMergeMode() == MergeMode.RIGHT_TO_LEFT) {
			affectedDiffs = Lists.newArrayList(Iterables
					.filter(differences, fromSide(DifferenceSource.RIGHT)));
			merger.copyAllRightToLeft(affectedDiffs, new BasicMonitor());
			addOrUpdateMergeData(affectedDiffs, getMergeMode());
		} else if (getMergeMode() == MergeMode.ACCEPT || getMergeMode() == MergeMode.REJECT) {
			affectedDiffs = acceptOrRejectWithoutConflicts(differences, leftToRight, mergerRegistry, merger);
		} else {
			throw new IllegalStateException();
		}

		return affectedDiffs;
	}

	/**
	 * Returns the {@link MergeOperation} for the given {@code diff}.
	 * <p>
	 * The merge operation will be different depending on whether the left-hand side and right-hand side are
	 * editable in the current context (i.e., the {@link #getMergeMode() merge mode}.
	 * </p>
	 * 
	 * @param diff
	 *            The difference to get the merge operation for.
	 * @return The merge operation.
	 */
	private MergeOperation getMergeOperation(Diff diff) {
		return getMergeMode().getMergeAction(diff, isLeftEditable(), isRightEditable());
	}

	/**
	 * Handles the merge of all non-conflicting differences in case of a two-way comparison without conflicts.
	 * 
	 * @param differences
	 *            The differences to be merged.
	 * @param leftToRight
	 *            The direction in which {@code differences} should be merged.
	 * @param mergerRegistry
	 *            The registry of mergers.
	 * @return an iterable over the differences that have actually been merged by this operation.
	 */
	private Iterable<Diff> mergeTwoWay(Collection<Diff> differences, boolean leftToRight,
			Registry mergerRegistry) {
		final List<Diff> affectedDiffs;
		final IBatchMerger merger = new BatchMerger(mergerRegistry);

		// in two-way comparison, difference source is always LEFT
		if (getMergeMode() == MergeMode.LEFT_TO_RIGHT) {
			affectedDiffs = Lists
					.newArrayList(Iterables.filter(differences, fromSide(DifferenceSource.LEFT)));
			merger.copyAllLeftToRight(affectedDiffs, new BasicMonitor());
			addOrUpdateMergeData(affectedDiffs, getMergeMode());
		} else if (getMergeMode() == MergeMode.RIGHT_TO_LEFT) {
			affectedDiffs = Lists
					.newArrayList(Iterables.filter(differences, fromSide(DifferenceSource.LEFT)));
			merger.copyAllRightToLeft(affectedDiffs, new BasicMonitor());
			addOrUpdateMergeData(affectedDiffs, getMergeMode());
		} else if (getMergeMode() == MergeMode.ACCEPT || getMergeMode() == MergeMode.REJECT) {
			affectedDiffs = acceptOrRejectWithoutConflicts(differences, leftToRight, mergerRegistry, merger);
		} else {
			throw new IllegalStateException();
		}

		return affectedDiffs;
	}

	/**
	 * Handles the merge of all non-conflicting differences in case of a comparison with conflicts.
	 * 
	 * @param differences
	 *            The differences to be merged.
	 * @param leftToRight
	 *            The direction in which {@code differences} should be merged.
	 * @param mergerRegistry
	 *            The registry of mergers.
	 * @return an iterable over the differences that have actually been merged by this operation.
	 */
	private Iterable<Diff> mergeWithConflicts(Collection<Diff> differences, boolean leftToRight,
			Registry mergerRegistry) {
		final List<Diff> affectedDiffs = new ArrayList<Diff>();
		final Monitor emfMonitor = new BasicMonitor();
		final Graph<Diff> differencesGraph = MergeDependenciesUtil.mapDifferences(differences,
				mergerRegistry, !leftToRight, getMergeMode());
		final PruningIterator<Diff> iterator = differencesGraph.breadthFirstIterator();

		while (iterator.hasNext()) {
			final Diff next = iterator.next();
			if (hasConflict(ConflictKind.REAL).apply(next)) {
				iterator.prune();
			} else {
				if (next.getState() != DifferenceState.MERGED) {
					affectedDiffs.add(next);
					final IMerger merger = mergerRegistry.getHighestRankingMerger(next);
					if (getMergeMode() == MergeMode.LEFT_TO_RIGHT) {
						merger.copyLeftToRight(next, emfMonitor);
					} else if (getMergeMode() == MergeMode.RIGHT_TO_LEFT) {
						merger.copyRightToLeft(next, emfMonitor);
					} else if (getMergeMode() == MergeMode.ACCEPT || getMergeMode() == MergeMode.REJECT) {
						MergeOperation mergeAction = getMergeOperation(next);
						if (mergeAction == MergeOperation.MARK_AS_MERGE) {
							markAsMerged(next, getMergeMode(), leftToRight, mergerRegistry);
						} else {
							if (isLeftEditable() && !leftToRight) {
								merger.copyRightToLeft(next, emfMonitor);
							} else if (isRightEditable() && leftToRight) {
								merger.copyLeftToRight(next, emfMonitor);
							}
						}
					} else {
						throw new IllegalStateException();
					}
				}
			}
		}
		addOrUpdateMergeData(affectedDiffs, getMergeMode());
		return affectedDiffs;
	}

	/**
	 * Performs an accept or reject operation in a three-way merge without conflicts or in a two-way merge.
	 * 
	 * @param differences
	 *            The differences to be merged.
	 * @param leftToRight
	 *            The direction in which {@code differences} should be merged.
	 * @param mergerRegistry
	 *            The registry of mergers.
	 * @param merger
	 *            The merger to be used in this operation.
	 * @return an iterable over the differences that have actually been merged by this operation.
	 */
	private List<Diff> acceptOrRejectWithoutConflicts(Collection<Diff> differences, boolean leftToRight,
			Registry mergerRegistry, final IBatchMerger merger) {
		final List<Diff> diffsToMarkAsMerged = newArrayList();
		final List<Diff> diffsToAccept = newArrayList();
		final List<Diff> diffsToReject = newArrayList();

		for (Diff diff : differences) {
			final MergeOperation mergeAction = getMergeOperation(diff);
			if (mergeAction == MergeOperation.MARK_AS_MERGE) {
				diffsToMarkAsMerged.add(diff);
			} else {
				if (isLeftEditable() && leftToRight) {
					diffsToReject.add(diff);
				} else {
					diffsToAccept.add(diff);
				}
			}
		}

		final Monitor emfMonitor = new BasicMonitor();
		mergeAll(diffsToAccept, leftToRight, merger, mergerRegistry, emfMonitor);
		mergeAll(diffsToReject, !leftToRight, merger, mergerRegistry, emfMonitor);
		markAllAsMerged(diffsToMarkAsMerged, getMergeMode(), mergerRegistry);

		final List<Diff> affectedDiffs = Lists.newArrayList(diffsToAccept);
		affectedDiffs.addAll(diffsToReject);
		affectedDiffs.addAll(diffsToMarkAsMerged);
		return affectedDiffs;
	}

	/**
	 * Checks whether the given comparison presents a real conflict.
	 * 
	 * @param comparison
	 *            The comparison to check for conflicts.
	 * @return <code>true</code> if there's at least one {@link ConflictKind#REAL real conflict} within this
	 *         comparison.
	 */
	private boolean hasRealConflict(Comparison comparison) {
		return any(comparison.getConflicts(), containsConflictOfTypes(ConflictKind.REAL));
	}

	/**
	 * Merge all given differences in case of an ACCEPT or REJECT MergeMode.
	 * 
	 * @param differences
	 *            The differences to merge.
	 * @param leftToRight
	 *            The direction in which {@code differences} should be merged.
	 * @param merger
	 *            The current merger.
	 * @param mergerRegistry
	 *            The registry of mergers.
	 * @param emfMonitor
	 *            To monitor the process.
	 */
	private void mergeAll(Collection<? extends Diff> differences, boolean leftToRight, IBatchMerger merger,
			Registry mergerRegistry, Monitor emfMonitor) {
		if (leftToRight) {
			merger.copyAllLeftToRight(differences, emfMonitor);
		} else {
			merger.copyAllRightToLeft(differences, emfMonitor);
		}

		for (Diff difference : differences) {
			final IMerger diffMerger = mergerRegistry.getHighestRankingMerger(difference);
			if (diffMerger instanceof IMerger2) {
				final Set<Diff> resultingMerges = MergeDependenciesUtil.getAllResultingMerges(difference,
						mergerRegistry, !leftToRight);
				addOrUpdateMergeData(resultingMerges, getMergeMode());

				final Set<Diff> resultingRejections = MergeDependenciesUtil.getAllResultingRejections(
						difference, mergerRegistry, !leftToRight);
				addOrUpdateMergeData(resultingRejections, getMergeMode().inverse());
			} else {
				addOrUpdateMergeData(Collections.singleton(difference), getMergeMode());
			}
		}
	}
}

Back to the top