Proper loading of the profiles and packages from the SRS
[EMFCompare2.git] / plugins / org.eclipse.emf.compare.ide.ui / src / org / eclipse / emf / compare / ide / ui / internal / logical / resolver / ModelsResolution.java
blobf2ad7668c334d6aeaf7c30d73620b16463a44b16
1 /*******************************************************************************
2 * Copyright (c) 2015 Obeo.
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 * Obeo - initial API and implementation
10 *******************************************************************************/
11 package org.eclipse.emf.compare.ide.ui.internal.logical.resolver;
13 import static com.google.common.base.Preconditions.checkArgument;
14 import static com.google.common.base.Preconditions.checkNotNull;
15 import static com.google.common.collect.Iterables.concat;
16 import static com.google.common.collect.Iterables.transform;
17 import static org.eclipse.emf.compare.ide.ui.internal.util.PlatformElementUtil.adaptAs;
18 import static org.eclipse.emf.compare.ide.utils.ResourceUtil.asURI;
20 import com.google.common.base.Function;
21 import com.google.common.base.Predicate;
22 import com.google.common.collect.Iterables;
23 import com.google.common.collect.Sets;
25 import java.util.Collections;
26 import java.util.LinkedHashSet;
27 import java.util.Set;
28 import java.util.concurrent.Callable;
30 import org.eclipse.core.resources.IFile;
31 import org.eclipse.core.resources.IStorage;
32 import org.eclipse.core.runtime.IProgressMonitor;
33 import org.eclipse.core.runtime.OperationCanceledException;
34 import org.eclipse.emf.common.util.URI;
35 import org.eclipse.emf.compare.ide.ui.internal.util.ThreadSafeProgressMonitor;
36 import org.eclipse.emf.compare.ide.ui.logical.IStorageProviderAccessor;
37 import org.eclipse.emf.compare.ide.ui.logical.IStorageProviderAccessor.DiffSide;
38 import org.eclipse.emf.compare.ide.ui.logical.SynchronizationModel;
39 import org.eclipse.emf.compare.ide.utils.ResourceUtil;
40 import org.eclipse.emf.compare.ide.utils.StorageTraversal;
41 import org.eclipse.emf.compare.ide.utils.StorageURIConverter;
43 /**
44 * Computation that resolves 2 or 3 storages (left, right and potentially origin).
46 * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
48 public class ModelsResolution extends AbstractResolution {
50 /** Storage Accessor. */
51 private final IStorageProviderAccessor storageAccessor;
53 /** The left storage. */
54 private final IStorage left;
56 /** The right storage. */
57 private final IStorage right;
59 /** The ancestor storage, can be <code>null</code>. */
60 private final IStorage origin;
62 /** The local resolver. */
63 private final IResourceDependencyLocalResolver localResolver;
65 /** The remote resolver. */
66 private final IResourceDependencyRemoteResolver remoteResolver;
68 /**
69 * At least one of {@link #left}, {@link #right} and {@link #origin} must be non-null.
71 * @param dependencyProvider
72 * The dependency provider
73 * @param scheduler
74 * The muti-thread support to use
75 * @param eventBus
76 * The event bus
77 * @param monitor
78 * The progress monitor to use to report progress
79 * @param storageAccessor
80 * The storage accessor, must not be {@code null}
81 * @param left
82 * The left storage, can be {@code null}
83 * @param right
84 * The right storage, can be {@code null}
85 * @param origin
86 * The ancestor storage, can be {@code null}
88 public ModelsResolution(IResolutionContext context, IProgressMonitor monitor,
89 IStorageProviderAccessor storageAccessor, IStorage left, IStorage right, IStorage origin) {
90 super(context, monitor);
91 this.localResolver = context.getLocalResolver();
92 this.remoteResolver = context.getRemoteResolver();
93 this.storageAccessor = checkNotNull(storageAccessor);
94 this.left = left;
95 this.right = right;
96 this.origin = origin;
97 checkArgument(left != null || right != null || origin != null);
101 * Executes the resolution.
103 * @return The logical model to use to compare the given storages
104 * @throws InterruptedException
105 * If the treatment is interrupted.
107 public SynchronizationModel run() throws InterruptedException {
108 if (logger.isDebugEnabled()) {
109 logger.debug("run() - START"); //$NON-NLS-1$
111 return call(new Callable<SynchronizationModel>() {
112 public SynchronizationModel call() throws Exception {
113 final IFile leftFile = adaptAs(left, IFile.class);
115 final SynchronizationModel synchronizationModel;
116 if (leftFile != null) {
117 synchronizationModel = resolveModelsWithLocal(leftFile, new ThreadSafeProgressMonitor(
118 monitor));
119 } else {
120 synchronizationModel = resolveRemoteModels(new ThreadSafeProgressMonitor(monitor));
122 if (logger.isDebugEnabled()) {
123 logger.debug("run() - FINISH"); //$NON-NLS-1$
125 return synchronizationModel;
131 * Overridden to set the work remaining to zero on the progress monitor used.
133 @Override
134 protected Runnable getFinalizeResolvingRunnable() {
135 return new Runnable() {
136 public void run() {
137 ModelsResolution.super.getFinalizeResolvingRunnable().run();
138 if (monitor != null) {
139 monitor.setWorkRemaining(0);
146 * The 'left' model we've been fed is a local file. We'll assume that the whole 'left' side of this
147 * comparison is local and resolve everything for that side as we would for local comparisons : update the
148 * dependency graph according to our resource listener, lookup for cross-references to/from the left
149 * resource according to the {@link #getResolutionScope() resolution scope}... Once we've resolved the
150 * local traversal, we'll use that as a base to infer the two remote sides, then "augment" it with the
151 * cross-references of the remote variants of these resources.
153 * @param leftFile
154 * File corresponding to the left side of this comparison.
155 * @param tspm
156 * Monitor on which to report progress to the user.
157 * @return The SynchronizationModel describing the traversals of all three sides of this logical model.
158 * @throws InterruptedException
159 * Thrown if the resolution is cancelled or interrupted one way or another.
161 private SynchronizationModel resolveModelsWithLocal(final IFile leftFile,
162 final ThreadSafeProgressMonitor tspm) throws InterruptedException {
163 if (logger.isDebugEnabled()) {
164 logger.debug("resolveModelsWithLocal() - updating dependencies"); //$NON-NLS-1$
166 // Update changes and compute dependencies for left
167 // Then load the same set of resources for the remote sides, completing it top-down
168 localResolver.updateDependencies(monitor, diagnostic, leftFile);
170 if (tspm.isCanceled()) {
171 throw new OperationCanceledException();
174 if (logger.isDebugEnabled()) {
175 logger.debug("resolveModelsWithLocal() - resolving traversal"); //$NON-NLS-1$
177 final Set<IStorage> leftTraversal = resolveTraversal(leftFile, Collections.<URI> emptySet());
179 if (logger.isDebugEnabled()) {
180 logger.debug("resolveModelsWithLocal() - resolving remote traversal"); //$NON-NLS-1$
182 return resolveRemoteTraversals(leftTraversal, tspm);
186 * All three sides we've been fed are remote. We'll resolve all three with a simple a top-down algorithm
187 * (detect only outgoing cross-references).
189 * @param tspm
190 * Monitor on which to report progress to the user.
191 * @return The SynchronizationModel describing the traversals of all three sides of this logical model.
192 * @throws InterruptedException
193 * Thrown if the resolution is cancelled or interrupted one way or another.
195 private SynchronizationModel resolveRemoteModels(ThreadSafeProgressMonitor tspm)
196 throws InterruptedException {
197 if (logger.isDebugEnabled()) {
198 logger.debug("resolveRemoteModels() - resolving left remote traversal"); //$NON-NLS-1$
200 final Set<IStorage> leftTraversal = resolveRemoteTraversal(left, Collections.<URI> emptySet(),
201 DiffSide.SOURCE, tspm);
202 if (logger.isDebugEnabled()) {
203 logger.debug("resolveRemoteModels() - resolving other remote traversals"); //$NON-NLS-1$
205 return resolveRemoteTraversals(leftTraversal, tspm);
209 * Resolve the remote sides (right and origin, or right alone in case of two-way) of this comparison,
210 * inferring a "starting traversal" from the left side.
211 * <p>
212 * Do note that {@code leftTraversal} <b>will be changed</b> as a result of this call if the right and/or
213 * origin sides contain a reference to another resource that was not found from the left
214 * cross-referencing, yet does exist in the left side.
215 * </p>
217 * @param leftTraversal
218 * The already resolved left traversal, to be augmented if right and/or origin have some new
219 * resources in their logical model.
220 * @param tspm
221 * Monitor on which to report progress to the user.
222 * @return The SynchronizationModel describing the traversals of all three sides of this logical model.
223 * @throws InterruptedException
224 * Thrown if the resolution is cancelled or interrupted one way or another.
226 private SynchronizationModel resolveRemoteTraversals(Set<IStorage> leftTraversal,
227 ThreadSafeProgressMonitor tspm) throws InterruptedException {
228 if (logger.isDebugEnabled()) {
229 logger.debug("resolveRemotetraversals() - START"); //$NON-NLS-1$
231 final Set<IStorage> rightTraversal = resolveRemoteTraversal(right, Iterables.transform(leftTraversal,
232 asURI()), DiffSide.REMOTE, tspm);
233 final Set<IStorage> differenceRightLeft = difference(rightTraversal, asURISet(leftTraversal));
234 loadAdditionalRemoteStorages(leftTraversal, rightTraversal, differenceRightLeft, tspm);
236 final Set<IStorage> originTraversal;
237 if (origin != null) {
238 final Set<URI> unionLeftRight = Sets.newLinkedHashSet(Iterables.transform(Sets.union(
239 leftTraversal, rightTraversal), asURI()));
240 originTraversal = resolveRemoteTraversal(origin, unionLeftRight, DiffSide.ORIGIN, tspm);
241 Set<IStorage> differenceOriginLeft = difference(originTraversal, asURISet(leftTraversal));
242 Set<IStorage> differenceOriginRight = difference(originTraversal, asURISet(rightTraversal));
243 Set<IStorage> additional = symmetricDifference(differenceOriginLeft, differenceOriginRight);
244 loadAdditionalRemoteStorages(leftTraversal, rightTraversal, originTraversal, additional, tspm);
245 } else {
246 originTraversal = Collections.emptySet();
248 final SynchronizationModel synchronizationModel = new SynchronizationModel(new StorageTraversal(
249 leftTraversal), new StorageTraversal(rightTraversal), new StorageTraversal(originTraversal),
250 diagnostic.getDiagnostic());
252 if (logger.isDebugEnabled()) {
253 logger.debug("resolveRemotetraversals() - FINISH"); //$NON-NLS-1$
255 return synchronizationModel;
259 * If we found some storages in the right traversal that were not part of the left traversal, we need to
260 * check whether they exist in the left, since in such a case they must be considered as part of the same
261 * logical model.
262 * <p>
263 * <b>Important</b> : note that the input {@code left} and {@code right} sets <b>will be modified</b> as a
264 * result of this call if there are any additional storages to load on these sides.
265 * </p>
267 * @param leftSet
268 * Traversal of the left logical model.
269 * @param rightSet
270 * Traversal of the right logical model.
271 * @param additional
272 * the addition storages we are to lookup in left.
273 * @param tspm
274 * Monitor on which to report progress to the user.
275 * @return The set of all additional resources (both on left and right) that have been loaded as a result
276 * of this call.
277 * @throws InterruptedException
278 * Thrown if the resolution is cancelled or interrupted one way or another.
280 private Set<IStorage> loadAdditionalRemoteStorages(Set<IStorage> leftSet, Set<IStorage> rightSet,
281 Set<IStorage> additional, ThreadSafeProgressMonitor tspm) throws InterruptedException {
283 * This loop will be extremely costly at best, but we hope the case to be sufficiently rare (and the
284 * new resources well spread when it happens) not to pose an issue in the most frequent cases.
287 final Set<IStorage> additionalStorages = new LinkedHashSet<IStorage>();
288 final Set<URI> additionalURIs = new LinkedHashSet<URI>();
289 // Have we found new resources in the right as compared to the left?
290 Set<IStorage> differenceRightLeft = additional;
291 boolean somethingToAdd = !differenceRightLeft.isEmpty();
292 while (somethingToAdd) {
293 somethingToAdd = false;
294 // There's at least one resource in the right that was not found in the left.
295 // This might be a new resource added on the right side... but it might also be a cross-reference
296 // that's been either removed from left or added in right. In this second case, we need the
297 // resource to be present in both traversals to make sure we'll be able to properly detect
298 // potential conflicts. However, since this resource could itself be a part of a larger logical
299 // model, we need to start the resolving again with it.
300 final Set<IStorage> additionalLeft = findAdditionalRemoteTraversal(leftSet, differenceRightLeft,
301 DiffSide.SOURCE, tspm);
302 if (leftSet.addAll(additionalLeft)) {
303 somethingToAdd = true;
304 for (IStorage storage : additionalLeft) {
305 final URI newURI = asURI().apply(storage);
306 if (additionalURIs.add(newURI)) {
307 additionalStorages.add(storage);
311 // have we only loaded the resources that were present in the right but not in the left, or have
312 // we found even more?
313 final Set<IStorage> differenceAdditionalLeftRight = difference(additionalLeft, asURISet(rightSet));
314 // If so, we once more need to augment the right traversal
315 final Set<IStorage> additionalRight = findAdditionalRemoteTraversal(rightSet,
316 differenceAdditionalLeftRight, DiffSide.REMOTE, tspm);
317 if (rightSet.addAll(additionalRight)) {
318 somethingToAdd = true;
319 for (IStorage storage : additionalRight) {
320 final URI newURI = asURI().apply(storage);
321 if (additionalURIs.add(newURI)) {
322 additionalStorages.add(storage);
326 // Start this loop anew if we once again augmented the right further than what we had in
327 // left
328 if (somethingToAdd) {
329 differenceRightLeft = difference(additionalRight, asURISet(leftSet));
332 return additionalStorages;
336 * If we found some storages in the origin traversal that were part of neither the left nor the right
337 * traversals, we need to check whether they exist in them, since in such a case they must be considered
338 * as part of the same logical model.
339 * <p>
340 * <b>Important</b> : note that the input {@code left}, {@code right} and {@code origin} sets <b>will be
341 * modified</b> as a result of this call if there are any additional storages to load on either side.
342 * </p>
344 * @param leftSet
345 * Traversal of the left logical model.
346 * @param rightSet
347 * Traversal of the right logical model.
348 * @param originSet
349 * Traversal of the origin logical model.
350 * @param additional
351 * the set of additional storages we are to lookup in right and left.
352 * @param tspm
353 * Monitor on which to report progress to the user.
354 * @throws InterruptedException
355 * Thrown if the resolution is cancelled or interrupted one way or another.
357 private void loadAdditionalRemoteStorages(Set<IStorage> leftSet, Set<IStorage> rightSet,
358 Set<IStorage> originSet, Set<IStorage> additional, ThreadSafeProgressMonitor tspm)
359 throws InterruptedException {
360 // This loop will be extremely costly at best, but we hope the case to be sufficiently rare (and the
361 // new resources well spread when it happens) not to pose an issue in the most frequent cases.
362 Set<IStorage> additionalStorages = additional;
363 while (!additionalStorages.isEmpty()) {
364 // There's at least one resource that is in the origin set yet neither in left nor in right.
365 final Set<IStorage> additionalLeftRightComparedToOrigin = loadAdditionalRemoteStorages(leftSet,
366 rightSet, additionalStorages, tspm);
367 // Have we found even more resources to add to the traversal? If so, augment the origin
368 // accordingly.
369 final Set<IStorage> additionalOrigin = findAdditionalRemoteTraversal(originSet,
370 additionalLeftRightComparedToOrigin, DiffSide.ORIGIN, tspm);
371 originSet.addAll(additionalOrigin);
372 // If we once again found new storages in the origin, restart the loop.
373 final Set<IStorage> differenceOriginLeft = difference(additionalOrigin, asURISet(leftSet));
374 final Set<IStorage> differenceOriginRight = difference(additionalOrigin, asURISet(rightSet));
375 additionalStorages = symmetricDifference(differenceOriginRight, differenceOriginLeft);
377 // Differences between left/right and origin could come from resources that are present in
378 // the origin, but were deleted in one of the sides. As these resources already exist in
379 // the origin, they need to be removed from the additionalStorages
380 additionalStorages.removeAll(originSet);
385 * Tries and resolve the given set of additional storages (as compared to {@code alreadyLoaded}) on the
386 * given side.
387 * <p>
388 * If the storages from {@code additionalStorages} do not (or no longer) exist on the given side, this
389 * will have no effect. Otherwise, they'll be loaded and resolved in order to determine whether they are
390 * part of a larger model. Whether they're part of a larger model or not, they will be returned by this
391 * method as long as they exist on the given side.
392 * </p>
394 * @param alreadyLoaded
395 * All storages that have already been loaded on the given side. This will prevent us from
396 * resolving the same model more than once.
397 * @param additionalStorages
398 * The set of additional storages we are to find and resolve on the given side.
399 * @param side
400 * Side on which we seek to load additional storages in the traversal.
401 * @param tspm
402 * Monitor on which to report progress to the user.
403 * @return The set of additional storages that are to be added to the traversal of the given side.
404 * @throws InterruptedException
405 * Thrown if the resolution is cancelled or interrupted one way or another.
407 private Set<IStorage> findAdditionalRemoteTraversal(Set<IStorage> alreadyLoaded,
408 Set<IStorage> additionalStorages, DiffSide side, final ThreadSafeProgressMonitor tspm)
409 throws InterruptedException {
410 if (additionalStorages.isEmpty()) {
411 return Collections.emptySet();
413 final SynchronizedResourceSet resourceSet = remoteResolver.getResourceSetForRemoteResolution(
414 diagnostic, tspm);
415 final StorageURIConverter converter = new RevisionedURIConverter(resourceSet.getURIConverter(),
416 storageAccessor, side);
417 resourceSet.setURIConverter(converter);
419 ResourceComputationScheduler<URI> scheduler = context.getScheduler();
420 scheduler.setComputedElements(transform(converter.getLoadedRevisions(), asURI()));
422 Iterable<URI> urisToResolve = transform(additionalStorages, asURI());
423 urisToResolve = Iterables.filter(urisToResolve, new Predicate<URI>() {
424 public boolean apply(URI input) {
425 return input != null && input.isPlatformResource();
428 scheduler.computeAll(transform(urisToResolve, resolveRemoteURI(tspm, resourceSet)));
430 resourceSet.dispose();
432 if (tspm.isCanceled()) {
433 throw new OperationCanceledException();
436 scheduler.clearComputedElements();
438 return converter.getLoadedRevisions();
442 * Returns the set of all elements that are contained neither in set1 nor in set2.
444 * @param set1
445 * First of the two sets.
446 * @param set2
447 * Second of the two sets.
448 * @return The set of all elements that are contained neither in set1 nor in set2.
450 private Set<IStorage> symmetricDifference(Set<IStorage> set1, Set<IStorage> set2) {
451 final Set<URI> uris1 = Sets.newLinkedHashSet(Iterables.transform(set1, asURI()));
452 final Set<URI> uris2 = Sets.newLinkedHashSet(Iterables.transform(set2, asURI()));
454 final Set<IStorage> symmetricDifference = new LinkedHashSet<IStorage>();
455 for (IStorage storage1 : set1) {
456 if (!uris2.contains(asURI().apply(storage1))) {
457 symmetricDifference.add(storage1);
460 for (IStorage storage2 : set2) {
461 if (!uris1.contains(asURI().apply(storage2))) {
462 symmetricDifference.add(storage2);
465 return symmetricDifference;
469 * Returns the set of all elements that are contained in {@code set1} but not in {@code set2}.
471 * @param set1
472 * First of the two sets.
473 * @param set2
474 * Second of the two sets.
475 * @return The set of all elements that are contained in {@code set1} but not in {@code set2}.
477 private Set<IStorage> difference(Set<IStorage> set1, Set<URI> set2) {
478 final Set<IStorage> difference = new LinkedHashSet<IStorage>();
479 for (IStorage storage1 : set1) {
480 final URI uri = asURI().apply(storage1);
481 if (!set2.contains(uri)) {
482 difference.add(storage1);
485 return difference;
489 * Resolve the remote traversal of the given storage.
491 * @param start
492 * Storage to resolve
493 * @param knownVariants
494 * Iterable over all the currently known {@link URI}s that are part of the local logical model
495 * on any side
496 * @param side
497 * The side
498 * @param tspm
499 * Monitor to report progress
500 * @return The traversal of the storage on the given side
501 * @throws InterruptedException
502 * If interrupted.
504 private Set<IStorage> resolveRemoteTraversal(IStorage start, Iterable<URI> knownVariants, DiffSide side,
505 final ThreadSafeProgressMonitor tspm) throws InterruptedException {
506 // we can't call ResourceUtil.createURIFor(start) if start is null
507 // but the returned Set must be changeable so Collections.emptySet() won't do
508 if (start == null) {
509 return Sets.newLinkedHashSet();
511 if (logger.isDebugEnabled()) {
512 logger.debug("resolveRemotetraversal() - START for " + start); //$NON-NLS-1$
514 final SynchronizedResourceSet resourceSet = remoteResolver.getResourceSetForRemoteResolution(
515 diagnostic, tspm);
516 final StorageURIConverter converter = new RevisionedURIConverter(resourceSet.getURIConverter(),
517 storageAccessor, side);
518 resourceSet.setURIConverter(converter);
520 ResourceComputationScheduler<URI> scheduler = context.getScheduler();
521 scheduler.clearComputedElements();
523 final URI startURI = ResourceUtil.createURIFor(start);
524 Iterable<URI> urisToResolve = concat(knownVariants, Collections.singleton(startURI));
525 scheduler.computeAll(transform(urisToResolve, resolveRemoteURI(tspm, resourceSet)));
527 if (tspm.isCanceled()) {
528 throw new OperationCanceledException();
531 scheduler.clearComputedElements();
533 resourceSet.dispose();
535 if (logger.isDebugEnabled()) {
536 logger.debug("resolveRemotetraversal() - END for " + start); //$NON-NLS-1$
538 return converter.getLoadedRevisions();
542 * Provides a {@link Function} that converts a givn URI into a Computation that can be run by a
543 * {@link ResourceComputationScheduler}.
545 * @param tspm
546 * The progress monitor to use
547 * @param resourceSet
548 * The resource set to use
549 * @return A {@link Function}, never {@code null}, that can be used to remotely resolvea given URI.
551 protected Function<URI, IComputation<URI>> resolveRemoteURI(final ThreadSafeProgressMonitor tspm,
552 final SynchronizedResourceSet resourceSet) {
553 return new Function<URI, IComputation<URI>>() {
554 public IComputation<URI> apply(final URI uri) {
555 return remoteResolver.getRemoteResolveComputation(resourceSet, uri, diagnostic, tspm);