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
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
;
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
;
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
;
69 * At least one of {@link #left}, {@link #right} and {@link #origin} must be non-null.
71 * @param dependencyProvider
72 * The dependency provider
74 * The muti-thread support to use
78 * The progress monitor to use to report progress
79 * @param storageAccessor
80 * The storage accessor, must not be {@code null}
82 * The left storage, can be {@code null}
84 * The right storage, can be {@code null}
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
);
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(
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.
134 protected Runnable
getFinalizeResolvingRunnable() {
135 return new Runnable() {
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.
154 * File corresponding to the left side of this comparison.
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).
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.
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.
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.
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
);
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
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.
268 * Traversal of the left logical model.
270 * Traversal of the right logical model.
272 * the addition storages we are to lookup in left.
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
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
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.
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.
345 * Traversal of the left logical model.
347 * Traversal of the right logical model.
349 * Traversal of the origin logical model.
351 * the set of additional storages we are to lookup in right and left.
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
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
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.
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.
400 * Side on which we seek to load additional storages in the traversal.
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(
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.
445 * First of the two sets.
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}.
472 * First of the two sets.
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
);
489 * Resolve the remote traversal of the given storage.
493 * @param knownVariants
494 * Iterable over all the currently known {@link URI}s that are part of the local logical model
499 * Monitor to report progress
500 * @return The traversal of the storage on the given side
501 * @throws InterruptedException
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
509 return Sets
.newLinkedHashSet();
511 if (logger
.isDebugEnabled()) {
512 logger
.debug("resolveRemotetraversal() - START for " + start
); //$NON-NLS-1$
514 final SynchronizedResourceSet resourceSet
= remoteResolver
.getResourceSetForRemoteResolution(
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}.
546 * The progress monitor to use
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
);