1 /*******************************************************************************
2 * Copyright (c) 2014 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
.collect
.Sets
.difference
;
14 import static com
.google
.common
.collect
.Sets
.intersection
;
15 import static org
.eclipse
.emf
.compare
.ide
.ui
.internal
.util
.PlatformElementUtil
.adaptAs
;
16 import static org
.eclipse
.emf
.compare
.ide
.utils
.ResourceUtil
.createURIFor
;
17 import static org
.eclipse
.emf
.compare
.ide
.utils
.ResourceUtil
.hasModelType
;
19 import com
.google
.common
.base
.Function
;
20 import com
.google
.common
.base
.Predicate
;
21 import com
.google
.common
.collect
.ImmutableSet
;
22 import com
.google
.common
.collect
.Iterables
;
23 import com
.google
.common
.collect
.Sets
;
24 import com
.google
.common
.util
.concurrent
.FutureCallback
;
25 import com
.google
.common
.util
.concurrent
.Futures
;
26 import com
.google
.common
.util
.concurrent
.ListenableFuture
;
27 import com
.google
.common
.util
.concurrent
.ListeningExecutorService
;
28 import com
.google
.common
.util
.concurrent
.MoreExecutors
;
29 import com
.google
.common
.util
.concurrent
.ThreadFactoryBuilder
;
31 import java
.util
.Collections
;
32 import java
.util
.HashSet
;
33 import java
.util
.LinkedHashSet
;
34 import java
.util
.List
;
36 import java
.util
.concurrent
.ExecutorService
;
37 import java
.util
.concurrent
.Executors
;
38 import java
.util
.concurrent
.ThreadFactory
;
39 import java
.util
.concurrent
.TimeUnit
;
40 import java
.util
.concurrent
.atomic
.AtomicBoolean
;
41 import java
.util
.concurrent
.locks
.Condition
;
42 import java
.util
.concurrent
.locks
.ReentrantLock
;
44 import org
.eclipse
.core
.resources
.IFile
;
45 import org
.eclipse
.core
.resources
.IResource
;
46 import org
.eclipse
.core
.resources
.IResourceChangeEvent
;
47 import org
.eclipse
.core
.resources
.IResourceChangeListener
;
48 import org
.eclipse
.core
.resources
.IResourceDelta
;
49 import org
.eclipse
.core
.resources
.IResourceDeltaVisitor
;
50 import org
.eclipse
.core
.resources
.IResourceVisitor
;
51 import org
.eclipse
.core
.resources
.IStorage
;
52 import org
.eclipse
.core
.resources
.ResourcesPlugin
;
53 import org
.eclipse
.core
.runtime
.CoreException
;
54 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
55 import org
.eclipse
.core
.runtime
.IStatus
;
56 import org
.eclipse
.core
.runtime
.OperationCanceledException
;
57 import org
.eclipse
.core
.runtime
.Path
;
58 import org
.eclipse
.core
.runtime
.Status
;
59 import org
.eclipse
.core
.runtime
.SubMonitor
;
60 import org
.eclipse
.emf
.common
.util
.BasicDiagnostic
;
61 import org
.eclipse
.emf
.common
.util
.Diagnostic
;
62 import org
.eclipse
.emf
.common
.util
.URI
;
63 import org
.eclipse
.emf
.compare
.ide
.ui
.internal
.EMFCompareIDEUIMessages
;
64 import org
.eclipse
.emf
.compare
.ide
.ui
.internal
.EMFCompareIDEUIPlugin
;
65 import org
.eclipse
.emf
.compare
.ide
.ui
.internal
.preferences
.EMFCompareUIPreferences
;
66 import org
.eclipse
.emf
.compare
.ide
.ui
.internal
.util
.ThreadSafeProgressMonitor
;
67 import org
.eclipse
.emf
.compare
.ide
.ui
.logical
.AbstractModelResolver
;
68 import org
.eclipse
.emf
.compare
.ide
.ui
.logical
.IModelResolver
;
69 import org
.eclipse
.emf
.compare
.ide
.ui
.logical
.IStorageProviderAccessor
;
70 import org
.eclipse
.emf
.compare
.ide
.ui
.logical
.IStorageProviderAccessor
.DiffSide
;
71 import org
.eclipse
.emf
.compare
.ide
.ui
.logical
.SynchronizationModel
;
72 import org
.eclipse
.emf
.compare
.ide
.utils
.ResourceUtil
;
73 import org
.eclipse
.emf
.compare
.ide
.utils
.StorageTraversal
;
74 import org
.eclipse
.emf
.compare
.ide
.utils
.StorageURIConverter
;
75 import org
.eclipse
.emf
.ecore
.resource
.Resource
;
76 import org
.eclipse
.emf
.ecore
.util
.EcoreUtil
;
77 import org
.eclipse
.jface
.preference
.IPreferenceStore
;
80 * This implementation of an {@link IModelResolver} will look up all of the models located in a set container
81 * level of the "starting point" (by default, the containing project) to construct the graph of dependencies
82 * between these models.
84 * Once this graph is created for the "local" resource, the right and origin (if any) resources will be
85 * inferred from the same traversal of resources, though this time expanded with a "top-down" approach : load
86 * all models of the traversal from the remote side, then resolve their containment tree to check whether
87 * there are other remote resources in the logical model that do not (or "that no longer) exist locally and
88 * thus couldn't be discovered in the first resolution phase.
91 * All model loading will happen concurrently. At first, a distinct thread will be launched to resolve every
92 * model discovered in the container we're browsing. Then, each thread can and will launch separate threads to
93 * resolve the set of dependencies discovered "under" the model they are in charge of resolving.
96 * No model will be loaded twice, since this will be aware of what models have already been resolved, thus
97 * ignoring duplicate resolving demands.
100 * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
102 public class ThreadedModelResolver
extends AbstractModelResolver
{
104 /** This can be used in order to convert an Iterable of IStorages to an Iterable over the storage's URIs. */
105 private static final Function
<IStorage
, URI
> AS_URI
= new Function
<IStorage
, URI
>() {
106 public URI
apply(IStorage input
) {
108 return createURIFor(input
);
115 * Keeps track of the discovered dependency graph for local resources.
117 * Model resolvers are created from the extension point registry, so we know there will be a single
118 * instance of our resolver for a single run of Eclipse (even across multiple comparisons). We also assume
119 * that this graph won't turn to be a memory hog since we're only keeping track of URIs, and at the worst
120 * no more URIs than there are resources in the user's workspace. We can thus keep this graph around to
121 * avoid multiple crawlings of the same models. Team, as well as the EMFResourceMapping, tend to be
122 * over-enthusiast with the resolution of model traversals. For example, a single
123 * "right-click -> compare with -> commit..." with EGit ends up calling 8 distinct times for the resource
124 * traversal of the selected resource.
127 private final Graph
<URI
> dependencyGraph
;
130 * We'll keep track of what's already been resolved to avoid duplicate jobs.
132 private Set
<URI
> resolvedResources
;
135 * Will keep track of any error or warning that can arise during the loading of the resources.
137 private BasicDiagnostic diagnostic
;
140 * Keeps track of the URIs which we are currently resolving (or which are queued for resolution).
142 * This along with {@link #resolvedResources} will prevent multiple "duplicate" resolution threads to be
143 * queued. We assume that this will be sufficient to prevent duplicates, and the resolution threads
144 * themselves won't check whether their target has already been resolved before starting.
147 private final Set
<URI
> currentlyResolving
;
149 /** Thread pool for our resolving threads. */
150 private ListeningExecutorService resolvingPool
;
152 /** Thread pool for our unloading threads. */
153 private ListeningExecutorService unloadingPool
;
156 * An executor service will be used to shut down the {@link #unloadingPool} and the {@link #resolvingPool}
159 private ListeningExecutorService terminator
;
161 /** Tracks if shutdown of {@link #unloadingPool} and {@link #resolvingPool} is currently in progress. */
162 private final AtomicBoolean shutdownInProgress
;
165 * This will lock will prevent concurrent modifications of this resolver's fields. Most notably,
166 * {@link #currentlyResolving}, {@link #resolvedResources} and {@link #diagnostic} must not be accessed
167 * concurrently by two threads at once.
169 private final ReentrantLock lock
;
172 * This resolver will not accept two model resolutions at once.
174 * Any thread trying to call a model resolution process through either of the three "resolve*" APIs will
175 * have to wait for this condition to be true before starting.
178 private final Condition notResolving
;
180 /** Condition to await for all current {@link ResourceResolver} threads to terminate. */
181 private final Condition resolutionEnd
;
184 * This resolver will keep a resource listener over the workspace in order to keep its dependencies graph
187 * We're building the dependency graph on-demand and keep it around between invocations. This listener
188 * will allow us to update the graph on-demand by keeping track of the removed and changed resources since
189 * we were last called.
192 private ModelResourceListener resourceListener
;
194 /** Default constructor. */
195 public ThreadedModelResolver() {
196 this.dependencyGraph
= new Graph
<URI
>();
197 this.lock
= new ReentrantLock(true);
198 this.notResolving
= lock
.newCondition();
199 this.resolutionEnd
= lock
.newCondition();
200 this.currentlyResolving
= new HashSet
<URI
>();
201 this.shutdownInProgress
= new AtomicBoolean(false);
205 * Creates the thread pools of this resolver. We cannot keep pools between resolving call because in case
206 * of cancellation, we have to shutdown the pool to exit early.
208 private void createThreadPools() {
209 final int availableProcessors
= Runtime
.getRuntime().availableProcessors();
210 ThreadFactory resolvingThreadFactory
= new ThreadFactoryBuilder().setNameFormat(
211 "EMFCompare-ResolvingThread-%d") //$NON-NLS-1$
213 this.resolvingPool
= MoreExecutors
.listeningDecorator(Executors
.newFixedThreadPool(
214 availableProcessors
, resolvingThreadFactory
));
215 ThreadFactory unloadingThreadFactory
= new ThreadFactoryBuilder().setNameFormat(
216 "EMFCompare-UnloadingThread-%d") //$NON-NLS-1$
218 this.unloadingPool
= MoreExecutors
.listeningDecorator(Executors
.newFixedThreadPool(
219 availableProcessors
, unloadingThreadFactory
));
226 public void initialize() {
227 this.resourceListener
= new ModelResourceListener();
228 ResourcesPlugin
.getWorkspace().addResourceChangeListener(resourceListener
);
230 this.terminator
= MoreExecutors
.listeningDecorator(Executors
231 .newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(
232 "EMFCompare-ThreadPoolShutdowner-%d").setPriority(Thread
.MAX_PRIORITY
).build())); //$NON-NLS-1$
237 public void dispose() {
238 terminator
.shutdown();
239 ResourcesPlugin
.getWorkspace().removeResourceChangeListener(resourceListener
);
244 * Shutdown {@link #resolvingPool} and {@link #unloadingPool} and set these two fields to null.
246 private void shutdownPools() {
247 if (!shutdownAndAwaitTermination(resolvingPool
) || !shutdownAndAwaitTermination(unloadingPool
)) {
248 EMFCompareIDEUIPlugin
.getDefault().log(IStatus
.WARNING
,
249 "Thread pools have not been properly stopped"); //$NON-NLS-1$
251 resolvingPool
= null;
252 unloadingPool
= null;
256 * Shuts down an {@link ExecutorService} in two phases, first by calling
257 * {@link ExecutorService#shutdown() shutdown} to reject incoming tasks, and then calling
258 * {@link ExecutorService#shutdownNow() shutdownNow}, if necessary, to cancel any lingering tasks. Returns
259 * true if the pool has been properly shutdown, false otherwise.
261 * Copy/pasted from {@link ExecutorService} javadoc.
264 * the pool to shutdown
265 * @return true if the pool has been properly shutdown, false otherwise.
267 private static boolean shutdownAndAwaitTermination(ExecutorService pool
) {
269 pool
.shutdown(); // Disable new tasks from being submitted
271 // Wait a while for existing tasks to terminate
272 if (!pool
.awaitTermination(5, TimeUnit
.SECONDS
)) {
273 pool
.shutdownNow(); // Cancel currently executing tasks
274 // Wait a while for tasks to respond to being canceled
275 if (!pool
.awaitTermination(5, TimeUnit
.SECONDS
)) {
279 } catch (InterruptedException ie
) {
280 // (Re-)Cancel if current thread also interrupted
282 // Preserve interrupt status
283 Thread
.currentThread().interrupt();
290 public boolean canResolve(IStorage sourceStorage
) {
297 * Note that no two threads will be able to resolve models at once : all three "resolve*" methods will
298 * lock internally to prevent multiple resolutions at once. Though this shouldn't happen unless the user
299 * calls multiple comparisons one after the other in quick succession, we use this locking to prevent
300 * potential unforeseen interactions.
303 public StorageTraversal
resolveLocalModel(IResource start
, IProgressMonitor monitor
)
304 throws InterruptedException
{
305 if (!(start
instanceof IFile
)) {
306 return new StorageTraversal(new LinkedHashSet
<IStorage
>());
309 ThreadSafeProgressMonitor subMonitor
= null;
310 lock
.lockInterruptibly();
312 subMonitor
= new ThreadSafeProgressMonitor(SubMonitor
.convert(monitor
, 100));
313 while (!currentlyResolving
.isEmpty()) {
314 notResolving
.await();
319 if (getResolutionScope() != CrossReferenceResolutionScope
.SELF
) {
320 final SynchronizedResourceSet resourceSet
= new SynchronizedResourceSet();
321 updateDependencies(resourceSet
, (IFile
)start
, subMonitor
);
322 updateChangedResources(resourceSet
, subMonitor
);
325 while (!currentlyResolving
.isEmpty()) {
326 resolutionEnd
.await();
329 if (subMonitor
.isCanceled()) {
330 throw new OperationCanceledException();
333 final Set
<IStorage
> traversalSet
= resolveTraversal((IFile
)start
, Collections
.<URI
> emptySet());
334 StorageTraversal traversal
= new StorageTraversal(traversalSet
, diagnostic
);
341 if (subMonitor
!= null) {
342 subMonitor
.setWorkRemaining(0);
345 notResolving
.signal();
354 * Note that no two threads will be able to resolve models at once : all three "resolve*" methods will
355 * lock internally to prevent multiple resolutions at once. Though this shouldn't happen unless the user
356 * calls multiple comparisons one after the other in quick succession, we use this locking to prevent
357 * potential unforeseen interactions.
360 public SynchronizationModel
resolveLocalModels(IResource left
, IResource right
, IResource origin
,
361 IProgressMonitor monitor
) throws InterruptedException
{
363 ThreadSafeProgressMonitor subMonitor
= new ThreadSafeProgressMonitor(SubMonitor
.convert(monitor
, 100));
365 if (!(left
instanceof IFile
&& right
instanceof IFile
&& (origin
== null || origin
instanceof IFile
))) {
366 return resolveNonFileLocalModels(left
, right
, origin
, subMonitor
);
368 return resolveFileLocalModel(left
, right
, origin
, subMonitor
);
371 subMonitor
.setWorkRemaining(0);
375 private SynchronizationModel
resolveNonFileLocalModels(IResource left
, IResource right
, IResource origin
,
376 ThreadSafeProgressMonitor subMonitor
) throws InterruptedException
{
377 // Sub-optimal implementation, we'll only try and resolve each side individually
378 final StorageTraversal leftTraversal
= resolveLocalModel(left
, subMonitor
);
379 final StorageTraversal rightTraversal
= resolveLocalModel(right
, subMonitor
);
380 final StorageTraversal originTraversal
;
381 if (origin
!= null) {
382 originTraversal
= resolveLocalModel(origin
, subMonitor
);
384 originTraversal
= new StorageTraversal(Sets
.<IStorage
> newLinkedHashSet());
387 return new SynchronizationModel(leftTraversal
, rightTraversal
, originTraversal
);
390 private SynchronizationModel
resolveFileLocalModel(IResource left
, IResource right
, IResource origin
,
391 ThreadSafeProgressMonitor monitor
) throws InterruptedException
{
393 lock
.lockInterruptibly();
395 while (!currentlyResolving
.isEmpty()) {
396 notResolving
.await();
401 if (getResolutionScope() != CrossReferenceResolutionScope
.SELF
) {
402 final SynchronizedResourceSet resourceSet
= new SynchronizedResourceSet();
403 updateDependencies(resourceSet
, (IFile
)left
, monitor
);
404 updateDependencies(resourceSet
, (IFile
)right
, monitor
);
405 if (origin
instanceof IFile
) {
406 updateDependencies(resourceSet
, (IFile
)origin
, monitor
);
408 updateChangedResources(resourceSet
, monitor
);
411 final URI leftURI
= createURIFor((IFile
)left
);
412 final URI rightURI
= createURIFor((IFile
)right
);
414 final Set
<IFile
> startingPoints
;
415 if (origin
instanceof IFile
) {
416 startingPoints
= ImmutableSet
.of((IFile
)left
, (IFile
)right
, (IFile
)origin
);
417 originURI
= createURIFor((IFile
)origin
);
419 startingPoints
= ImmutableSet
.of((IFile
)left
, (IFile
)right
);
423 while (!currentlyResolving
.isEmpty()) {
424 resolutionEnd
.await();
427 if (monitor
.isCanceled()) {
428 throw new OperationCanceledException();
431 final Set
<IStorage
> leftTraversal
;
432 final Set
<IStorage
> rightTraversal
;
433 final Set
<IStorage
> originTraversal
;
434 if (origin
instanceof IFile
) {
435 leftTraversal
= resolveTraversal((IFile
)left
, ImmutableSet
.of(rightURI
, originURI
));
436 rightTraversal
= resolveTraversal((IFile
)right
, ImmutableSet
.of(leftURI
, originURI
));
437 originTraversal
= resolveTraversal((IFile
)origin
, ImmutableSet
.of(leftURI
, rightURI
));
439 leftTraversal
= resolveTraversal((IFile
)left
, Collections
.singleton(rightURI
));
440 rightTraversal
= resolveTraversal((IFile
)right
, Collections
.singleton(leftURI
));
441 originTraversal
= Collections
.emptySet();
445 * If one resource of the logical model was pointing to both (or "all three") of our starting
446 * elements, we'll have way too many things in our traversal. We need to remove the intersection
447 * before going any further.
449 Set
<IStorage
> intersection
= intersection(leftTraversal
, rightTraversal
);
450 if (!originTraversal
.isEmpty()) {
451 intersection
= intersection(intersection
, originTraversal
);
453 logCoherenceThreats(Iterables
.transform(startingPoints
, AS_URI
), Iterables
.transform(
454 intersection
, AS_URI
));
456 final Set
<IStorage
> actualLeft
= new LinkedHashSet
<IStorage
>(difference(leftTraversal
,
458 final Set
<IStorage
> actualRight
= new LinkedHashSet
<IStorage
>(difference(rightTraversal
,
460 final Set
<IStorage
> actualOrigin
= new LinkedHashSet
<IStorage
>(difference(originTraversal
,
462 final SynchronizationModel synchronizationModel
= new SynchronizationModel(new StorageTraversal(
463 actualLeft
), new StorageTraversal(actualRight
), new StorageTraversal(actualOrigin
),
466 return synchronizationModel
;
471 notResolving
.signal();
480 * Note that no two threads will be able to resolve models at once : all three "resolve*" methods will
481 * lock internally to prevent multiple resolutions at once. Though this shouldn't happen unless the user
482 * calls multiple comparisons one after the other in quick succession, we use this locking to prevent
483 * potential unforeseen interactions.
486 public SynchronizationModel
resolveModels(IStorageProviderAccessor storageAccessor
, IStorage left
,
487 IStorage right
, IStorage origin
, IProgressMonitor monitor
) throws InterruptedException
{
489 ThreadSafeProgressMonitor subMonitor
= null;
490 lock
.lockInterruptibly();
492 subMonitor
= new ThreadSafeProgressMonitor(SubMonitor
.convert(monitor
, 100));
494 while (!currentlyResolving
.isEmpty()) {
495 notResolving
.await();
500 final IFile leftFile
= adaptAs(left
, IFile
.class);
503 * If we have a local side to this comparison, resolve it first, then use this result to infer the
504 * remote sides' traversal. Otherwise, resolve all three side in a simple top-down approach.
506 final SynchronizationModel synchronizationModel
;
507 if (leftFile
!= null) {
508 synchronizationModel
= resolveModelsWithLocal(storageAccessor
, leftFile
, right
, origin
,
511 synchronizationModel
= resolveRemoteModels(storageAccessor
, left
, right
, origin
, subMonitor
);
514 return synchronizationModel
;
519 if (subMonitor
!= null) {
520 subMonitor
.setWorkRemaining(0);
523 notResolving
.signal();
530 * This should be call before starting any model resolution but it must not be call if another resolution
531 * is already running (i.e. it must be call after an {@link #notResolving}.await()).
533 private void setupResolving() {
535 resolvedResources
= new LinkedHashSet
<URI
>();
536 diagnostic
= new BasicDiagnostic(EMFCompareIDEUIPlugin
.PLUGIN_ID
, 0, null, new Object
[0]);
540 * This is the counterpart of the {@link #setupResolving()} method. This should be called in a finally
541 * block everywhere {@link #setupResolving()} is called.
543 private void finalizeResolving() {
544 if (!shutdownInProgress
.get()) {
548 if (diagnostic
.getSeverity() >= Diagnostic
.ERROR
) {
549 // something bad (or a cancel request) happened during resolution, so we invalidate the
550 // dependency graph to avoid weird behavior next time the resolution is called.
551 dependencyGraph
.clear();
554 resolvedResources
= null;
559 * The 'left' model we've been fed is a local file. We'll assume that the whole 'left' side of this
560 * comparison is local and resolve everything for that side as we would for local comparisons : update the
561 * dependency graph according to our resource listener, lookup for cross-references to/from the left
562 * resource according to the {@link #getResolutionScope() resolution scope}... Once we've resolved the
563 * local traversal, we'll use that as a base to infer the two remote sides, then "augment" it with the
564 * outgoing references of the remote variants of these resources.
566 * @param storageAccessor
567 * The accessor that can be used to retrieve synchronization information between our resources.
569 * File corresponding to the left side of this comparison.
571 * "starting point" of the traversal to resolve as the right logical model.
573 * "starting point" of the traversal to resolve as the origin logical model (common ancestor of
574 * left and right). Can be <code>null</code>.
576 * Monitor on which to report progress to the user.
577 * @return A traversal corresponding to all resources composing the given file's logical model.
578 * @throws InterruptedException
579 * Thrown if the resolution is cancelled or interrupted one way or another.
581 private SynchronizationModel
resolveModelsWithLocal(IStorageProviderAccessor storageAccessor
, IFile left
,
582 IStorage right
, IStorage origin
, ThreadSafeProgressMonitor monitor
) throws InterruptedException
{
584 // Update changes and compute dependencies for left
585 // Then load the same set of resources for the remote sides, completing it top-down
587 if (getResolutionScope() != CrossReferenceResolutionScope
.SELF
) {
588 final SynchronizedResourceSet resourceSet
= new SynchronizedResourceSet();
589 updateDependencies(resourceSet
, left
, monitor
);
590 updateChangedResources(resourceSet
, monitor
);
593 while (!currentlyResolving
.isEmpty()) {
594 resolutionEnd
.await();
596 if (monitor
.isCanceled()) {
597 throw new OperationCanceledException();
599 final Set
<IStorage
> leftTraversal
= resolveTraversal(left
, Collections
.<URI
> emptySet());
601 final Set
<IStorage
> rightTraversal
= resolveRemoteTraversal(storageAccessor
, right
, leftTraversal
,
602 DiffSide
.REMOTE
, monitor
);
603 final Set
<IStorage
> originTraversal
;
604 if (origin
!= null) {
605 originTraversal
= resolveRemoteTraversal(storageAccessor
, origin
, leftTraversal
, DiffSide
.ORIGIN
,
608 originTraversal
= Collections
.emptySet();
611 final SynchronizationModel synchronizationModel
= new SynchronizationModel(new StorageTraversal(
612 leftTraversal
), new StorageTraversal(rightTraversal
), new StorageTraversal(originTraversal
),
615 return synchronizationModel
;
619 * All three sides we've been fed are remote. We'll resolve all three with a simple a top-down algorithm
620 * (detect only outgoing cross-references).
622 * @param storageAccessor
623 * The accessor that can be used to retrieve synchronization information between our resources.
625 * "starting point" of the traversal to resolve as the left logical model.
627 * "starting point" of the traversal to resolve as the right logical model.
629 * "starting point" of the traversal to resolve as the origin logical model (common ancestor of
630 * left and right). Can be <code>null</code>.
632 * Monitor on which to report progress to the user.
633 * @return A traversal corresponding to all resources composing the given file's logical model.
634 * @throws InterruptedException
635 * Thrown if the resolution is cancelled or interrupted one way or another.
637 private SynchronizationModel
resolveRemoteModels(IStorageProviderAccessor storageAccessor
, IStorage left
,
638 IStorage right
, IStorage origin
, ThreadSafeProgressMonitor monitor
) throws InterruptedException
{
640 final Set
<IStorage
> leftTraversal
= resolveRemoteTraversal(storageAccessor
, left
, Collections
641 .<IStorage
> emptySet(), DiffSide
.SOURCE
, monitor
);
642 final Set
<IStorage
> rightTraversal
= resolveRemoteTraversal(storageAccessor
, right
, Collections
643 .<IStorage
> emptySet(), DiffSide
.REMOTE
, monitor
);
644 final Set
<IStorage
> originTraversal
;
645 if (origin
!= null) {
646 originTraversal
= resolveRemoteTraversal(storageAccessor
, origin
, Collections
647 .<IStorage
> emptySet(), DiffSide
.ORIGIN
, monitor
);
649 originTraversal
= Collections
.emptySet();
651 final SynchronizationModel synchronizationModel
= new SynchronizationModel(new StorageTraversal(
652 leftTraversal
), new StorageTraversal(rightTraversal
), new StorageTraversal(originTraversal
),
655 return synchronizationModel
;
659 * Checks the current state of our {@link #resourceListener} and updates the dependency graph for all
660 * resources that have been changed since we last checked.
663 * The resource set in which to load our temporary resources.
665 * Monitor on which to report progress to the user.
667 private void updateChangedResources(SynchronizedResourceSet resourceSet
, ThreadSafeProgressMonitor monitor
) {
668 final Set
<URI
> removedURIs
= difference(resourceListener
.popRemovedURIs(), resolvedResources
);
669 final Set
<URI
> changedURIs
= difference(resourceListener
.popChangedURIs(), resolvedResources
);
671 dependencyGraph
.removeAll(removedURIs
);
673 // We need to re-resolve the changed resources, along with their direct parents
674 final Set
<URI
> recompute
= new LinkedHashSet
<URI
>(changedURIs
);
675 for (URI changed
: changedURIs
) {
676 if (dependencyGraph
.contains(changed
)) {
677 recompute
.addAll(dependencyGraph
.getDirectParents(changed
));
680 dependencyGraph
.removeAll(recompute
);
682 for (URI changed
: recompute
) {
683 demandResolve(resourceSet
, changed
, monitor
);
688 * Update the dependency graph to make sure that it contains the given file.
690 * If the graph does not yet contain this file, we'll try and find cross-references outgoing from and/or
691 * incoming to the given file, depending on the current {@link #getResolutionScope() resolution scope}.
695 * The resource set in which to load the temporary resources.
697 * The file which we need to be present in the dependency graph.
699 * Monitor on which to report progress to the user.
701 private void updateDependencies(SynchronizedResourceSet resourceSet
, IFile file
,
702 ThreadSafeProgressMonitor monitor
) {
703 final URI expectedURI
= createURIFor(file
);
704 if (!dependencyGraph
.contains(expectedURI
)) {
705 final IResource startingPoint
= getResolutionStartingPoint(file
);
706 final ModelResourceVisitor modelVisitor
= new ModelResourceVisitor(resourceSet
, monitor
);
708 startingPoint
.accept(modelVisitor
);
709 } catch (CoreException e
) {
710 safeMergeDiagnostic(BasicDiagnostic
.toDiagnostic(e
));
716 * Returns the starting point for the resolution of the given file's logical model according to
717 * {@link #getResolutionScope()}.
720 * The file which logical model we need to add to the current {@link #dependencyGraph}.
721 * @return Starting point for this file's logical model resolution.
722 * @see CrossReferenceResolutionScope
724 private IResource
getResolutionStartingPoint(IFile file
) {
725 final IResource startingPoint
;
726 switch (getResolutionScope()) {
728 startingPoint
= ResourcesPlugin
.getWorkspace().getRoot();
731 startingPoint
= file
.getProject();
734 startingPoint
= file
.getParent();
737 // fall through, the difference between SELF and OUTGOING will only come later on
741 startingPoint
= file
;
744 return startingPoint
;
748 * Tells this resolver how much of the dependency graph should be created at once. Note that this value
749 * may change during a resolution, which sole "visible" effect would be to prevent resolution of further
750 * outgoing references if the new value is "SELF".
752 * @return The current resolution scope.
754 private CrossReferenceResolutionScope
getResolutionScope() {
755 final IPreferenceStore store
= EMFCompareIDEUIPlugin
.getDefault().getPreferenceStore();
756 if (store
.getBoolean(EMFCompareUIPreferences
.DISABLE_RESOLVERS_PREFERENCE
)) {
757 return CrossReferenceResolutionScope
.SELF
;
759 final String stringValue
= store
.getString(EMFCompareUIPreferences
.RESOLUTION_SCOPE_PREFERENCE
);
760 return CrossReferenceResolutionScope
.valueOf(stringValue
);
763 private Set
<IStorage
> resolveTraversal(IFile file
, Set
<URI
> bounds
) {
764 final Set
<IStorage
> traversal
= new LinkedHashSet
<IStorage
>();
766 final Iterable
<URI
> dependencies
= getDependenciesOf(file
, bounds
);
767 for (URI uri
: dependencies
) {
768 traversal
.add(getFileAt(uri
));
773 private Set
<IStorage
> resolveRemoteTraversal(IStorageProviderAccessor storageAccessor
, IStorage start
,
774 Set
<IStorage
> localVariants
, DiffSide side
, ThreadSafeProgressMonitor monitor
)
775 throws InterruptedException
{
776 final SynchronizedResourceSet resourceSet
= new SynchronizedResourceSet();
777 final StorageURIConverter converter
= new RevisionedURIConverter(resourceSet
.getURIConverter(),
778 storageAccessor
, side
);
779 resourceSet
.setURIConverter(converter
);
781 resolvedResources
= new LinkedHashSet
<URI
>();
783 for (IStorage local
: localVariants
) {
785 * FIXME check that the IResourceVariantTrees support parallel accesses... or make sure that we do
786 * not use multiple thread to resolve the remote variants. For now, we'll use threads.
788 final URI expectedURI
= ResourceUtil
.createURIFor(local
);
789 demandRemoteResolve(resourceSet
, expectedURI
, monitor
);
792 final URI startURI
= ResourceUtil
.createURIFor(start
);
793 demandRemoteResolve(resourceSet
, startURI
, monitor
);
795 while (!currentlyResolving
.isEmpty()) {
796 resolutionEnd
.await();
799 if (monitor
.isCanceled()) {
800 throw new OperationCanceledException();
803 resolvedResources
= null;
805 return converter
.getLoadedRevisions();
808 private Iterable
<URI
> getDependenciesOf(IFile file
, Set
<URI
> bounds
) {
809 final URI expectedURI
= ResourceUtil
.createURIFor(file
);
811 final Iterable
<URI
> dependencies
;
812 switch (getResolutionScope()) {
814 dependencies
= dependencyGraph
.getSubgraphContaining(expectedURI
, bounds
);
817 final Set
<URI
> allDependencies
= dependencyGraph
.getSubgraphContaining(expectedURI
, bounds
);
818 final IResource project
= file
.getProject();
819 dependencies
= Iterables
.filter(allDependencies
, isInContainer(project
));
822 final Set
<URI
> allDependencies1
= dependencyGraph
.getSubgraphContaining(expectedURI
, bounds
);
823 final IResource container
= file
.getParent();
824 dependencies
= Iterables
.filter(allDependencies1
, isInContainer(container
));
827 dependencies
= dependencyGraph
.getTreeFrom(expectedURI
, bounds
);
832 dependencies
= Collections
.singleton(expectedURI
);
839 * Returns the IFile located at the given URI.
842 * URI we need the file for.
843 * @return The IFile located at the given URI.
845 private IFile
getFileAt(URI uri
) {
846 final StringBuilder path
= new StringBuilder();
847 List
<String
> segments
= uri
.segmentsList();
848 if (uri
.isPlatformResource()) {
849 segments
= segments
.subList(1, segments
.size());
851 for (String segment
: segments
) {
852 path
.append(URI
.decode(segment
)).append('/');
854 return ResourcesPlugin
.getWorkspace().getRoot().getFile(new Path(path
.toString()));
858 * This predicate can be used to check wether a given URI points to a workspace resource contained in the
862 * The container in which we need the resources to be contained.
863 * @return A ready to use predicate.
865 private Predicate
<URI
> isInContainer(final IResource container
) {
866 return new Predicate
<URI
>() {
867 public boolean apply(URI input
) {
869 final IFile pointedFile
= getFileAt(input
);
870 if (pointedFile
!= null) {
871 return container
.getLocation().isPrefixOf(pointedFile
.getLocation());
880 * When executing local comparisons, we resolve the full logical model of both (or "all three of") the
883 * If there is one resource in the scope that references all of these starting points, then we'll have
884 * perfectly identical logical models for all comparison sides. Because of that, we need to constrain the
885 * logical model of each starting point to only parts that are not accessible from other starting points.
886 * This might cause coherence issues as merging could thus "break" references from other files to our
890 * This method will be used to browse the files that are removed from the logical model, and log a warning
891 * for the files that are removed even though they are "parents" of one of the starting points.
894 * @param startingPoints
895 * Starting points of the comparison.
896 * @param removedFromModel
897 * All files that have been removed from the comparison scope.
899 private void logCoherenceThreats(Iterable
<URI
> startingPoints
, Iterable
<URI
> removedFromModel
) {
900 final Set
<URI
> coherenceThreats
= new LinkedHashSet
<URI
>();
901 for (URI start
: startingPoints
) {
902 for (URI removed
: removedFromModel
) {
903 if (dependencyGraph
.hasChild(removed
, start
)) {
904 coherenceThreats
.add(removed
);
909 if (!coherenceThreats
.isEmpty()) {
910 // FIXME: should be added to diagnostic instead
911 final String message
= EMFCompareIDEUIMessages
.getString("ModelResolver.coherenceWarning"); //$NON-NLS-1$
912 final String details
= Iterables
.toString(coherenceThreats
);
913 EMFCompareIDEUIPlugin
.getDefault().getLog().log(
914 new Status(IStatus
.WARNING
, EMFCompareIDEUIPlugin
.PLUGIN_ID
, message
+ '\n' + details
));
919 * Allows callers to launch the loading and resolution of the model pointed at by the given URI.
921 * This will check whether the given storage isn't already being resolved, then submit a job to the
922 * {@link #resolvingPool} to load and resolve the model in a separate thread.
926 * The resource set in which to load the resource.
928 * The uri we are to try and load as a model.
930 * Monitor on which to report progress to the user.
931 * @see ResourceResolver
933 protected void demandResolve(SynchronizedResourceSet resourceSet
, URI uri
,
934 final ThreadSafeProgressMonitor monitor
) {
935 if (isInterruptedOrCanceled(monitor
)) {
936 demandResolvingAndUnloadingPoolShutdown();
942 if (resolvedResources
.add(uri
) && currentlyResolving
.add(uri
)) {
943 // Regardless of the amount of progress reported so far, use 0.1% of the space remaining in
944 // the monitor to process the next node.
945 monitor
.setWorkRemaining(1000);
946 ListenableFuture
<?
> future
= resolvingPool
.submit(new ResourceResolver(resourceSet
, uri
,
948 Futures
.addCallback(future
, new ResolvingFutureCallback(monitor
, uri
));
956 * Allows callers to launch the loading and resolution of the model pointed at by the given URI, without
957 * updating the {@link #dependencyGraph} along the way.
959 * This will check whether the given storage isn't already being resolved, then submit a job to the
960 * {@link #resolvingPool} to load and resolve the model in a separate thread.
964 * The resource set in which to load the resource.
966 * The uri we are to try and load as a model.
968 * Monitor on which to report progress to the user.
970 protected void demandRemoteResolve(SynchronizedResourceSet resourceSet
, URI uri
,
971 final ThreadSafeProgressMonitor monitor
) {
972 if (isInterruptedOrCanceled(monitor
)) {
973 demandResolvingAndUnloadingPoolShutdown();
979 if (resolvedResources
.add(uri
) && currentlyResolving
.add(uri
)) {
980 // Regardless of the amount of progress reported so far, use 0.1% of the space remaining in
981 // the monitor to process the next node.
982 monitor
.setWorkRemaining(1000);
983 ListenableFuture
<?
> future
= resolvingPool
.submit(new RemoteResourceResolver(resourceSet
,
985 Futures
.addCallback(future
, new ResolvingFutureCallback(monitor
, uri
));
993 * Allows callers to launch the unloading of the given resource.
995 * Do note that even though this is called "unload", we won't actually call {@link Resource#unload()} on
996 * the given resource unless we deem it necessary (we only call if for UML because of the CacheAdapter)
997 * for now. This will only remove the resource from its containing resource set so as to allow it to be
1001 * @param resourceSet
1002 * The resource set containing the resource to be unloaded.
1004 * The resource to unload.
1006 * Monitor on which to report progress to the user.
1007 * @see ResourceUnloader
1009 protected void demandUnload(SynchronizedResourceSet resourceSet
, Resource resource
,
1010 final ThreadSafeProgressMonitor monitor
) {
1012 // Regardless of the amount of progress reported so far, use 0.1% of the space remaining in the
1013 // monitor to process the next node.
1014 monitor
.setWorkRemaining(1000);
1015 ListenableFuture
<?
> future
= unloadingPool
1016 .submit(new ResourceUnloader(resourceSet
, resource
, monitor
));
1017 Futures
.addCallback(future
, new FutureCallback
<Object
>() {
1018 public void onSuccess(Object result
) {
1019 if (!isInterruptedOrCanceled(monitor
)) {
1024 public void onFailure(Throwable t
) {
1025 if (!isInterruptedOrCanceled(monitor
)) {
1027 safeMergeDiagnostic(BasicDiagnostic
.toDiagnostic(t
));
1034 * Thread safely merge the given diagnostic to the {@link #diagnostic} field.
1036 * @param resourceDiagnostic
1037 * the diagnostic to be added to the global diagnostic.
1039 private void safeMergeDiagnostic(Diagnostic resourceDiagnostic
) {
1042 diagnostic
.merge(resourceDiagnostic
);
1049 * Checks if the current thread is interrupted or if the given monitor has been canceled.
1052 * the monitor to check
1053 * @return true if the current thread has been canceled, false otherwise.
1055 private boolean isInterruptedOrCanceled(IProgressMonitor monitor
) {
1056 return Thread
.currentThread().isInterrupted() || monitor
.isCanceled();
1060 * If {@link #shutdownInProgress shutdown has not been requested before}, it submits a new task to
1061 * {@link #shutdownPools() shut down} {@link #resolvingPool} and {@link #unloadingPool}. Do nothing if
1062 * current thread already is interrupted.
1064 private void demandResolvingAndUnloadingPoolShutdown() {
1065 if (!Thread
.currentThread().isInterrupted()) {
1066 if (shutdownInProgress
.compareAndSet(false, true)) {
1067 Runnable runnable
= new Runnable() {
1073 ListenableFuture
<?
> listenableFuture
= terminator
.submit(runnable
);
1074 Futures
.addCallback(listenableFuture
, new FutureCallback
<Object
>() {
1075 public void onSuccess(Object result
) {
1076 shutdownInProgress
.set(false);
1079 public void onFailure(Throwable t
) {
1080 shutdownInProgress
.set(false);
1081 EMFCompareIDEUIPlugin
.getDefault().log(t
);
1089 * This will remove the given uri from the {@link #currentlyResolving} set and signal to
1090 * {@link #resolutionEnd} if the set is empty afterward. This method must be call by every callback of
1094 * the uri to remove.
1096 private void finalizeResolvingTask(URI uri
) {
1099 currentlyResolving
.remove(uri
);
1100 if (currentlyResolving
.isEmpty()) {
1101 resolutionEnd
.signal();
1109 * The callback for {@link ResourceResolver} and {@link RemoteResourceResolver} tasks. It will report
1110 * progress, log errors and finalize the resolving and as such, possibly signaling the end of the
1113 * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
1115 private final class ResolvingFutureCallback
implements FutureCallback
<Object
> {
1117 /** The monitor to which report progress. */
1118 private final IProgressMonitor monitor
;
1120 private final URI uri
;
1125 private ResolvingFutureCallback(IProgressMonitor monitor
, URI uri
) {
1126 this.monitor
= monitor
;
1130 public void onSuccess(Object result
) {
1132 if (!isInterruptedOrCanceled(monitor
)) {
1133 // do not report progress anymore when the task has been interrupted of canceled. It
1134 // speeds up the cancellation.
1138 finalizeResolvingTask(uri
);
1142 public void onFailure(Throwable t
) {
1144 if (!isInterruptedOrCanceled(monitor
)) {
1145 // do not report progress or errors anymore when the task has been interrupted of
1146 // canceled. It speeds up the cancellation.
1148 safeMergeDiagnostic(BasicDiagnostic
.toDiagnostic(t
));
1151 finalizeResolvingTask(uri
);
1157 * Implements a runnable that will load the EMF resource pointed at by a given URI, then resolve all of
1158 * its cross-referenced resources and update the dependency graph accordingly.
1160 * Once done with the resolution, this thread will spawn an independent job to unload the resource.
1163 * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
1165 private class ResourceResolver
implements Runnable
{
1166 /** The resource set in which to load the resource. */
1167 private final SynchronizedResourceSet resourceSet
;
1169 /** URI that needs to be loaded as an EMF model. */
1170 private final URI uri
;
1172 /** Monitor on which to report progress to the user. */
1173 private final ThreadSafeProgressMonitor monitor
;
1176 * Default constructor.
1178 * @param resourceSet
1179 * The resource set in which to load the resource.
1181 * URI that needs to be loaded as an EMF model.
1183 * Monitor on which to report progress to the user.
1185 public ResourceResolver(SynchronizedResourceSet resourceSet
, URI uri
,
1186 ThreadSafeProgressMonitor monitor
) {
1187 this.resourceSet
= resourceSet
;
1189 this.monitor
= monitor
;
1192 /** {@inheritDoc} */
1194 if (isInterruptedOrCanceled(monitor
)) {
1195 demandResolvingAndUnloadingPoolShutdown();
1199 final Resource resource
= resourceSet
.loadResource(uri
);
1200 Diagnostic resourceDiagnostic
= EcoreUtil
.computeDiagnostic(resource
, true);
1201 if (resourceDiagnostic
.getSeverity() >= Diagnostic
.WARNING
) {
1202 safeMergeDiagnostic(resourceDiagnostic
);
1204 dependencyGraph
.add(uri
);
1205 if (getResolutionScope() != CrossReferenceResolutionScope
.SELF
) {
1206 final Set
<URI
> crossReferencedResources
= resourceSet
.discoverCrossReferences(resource
,
1208 dependencyGraph
.addChildren(uri
, crossReferencedResources
);
1209 for (URI crossRef
: crossReferencedResources
) {
1210 if (isInterruptedOrCanceled(monitor
)) {
1211 demandResolvingAndUnloadingPoolShutdown();
1212 // do not return, we want to unload what we've already loaded to avoid leaks.
1215 demandResolve(resourceSet
, crossRef
, monitor
);
1218 demandUnload(resourceSet
, resource
, monitor
);
1223 * Implements a runnable that will load the given EMF resource, then launch the resolution of all
1224 * cross-references of this resource in separate threads. This will not update the dependency graph.
1226 * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
1228 private class RemoteResourceResolver
implements Runnable
{
1229 /** The resource set in which to load the resource. */
1230 private final SynchronizedResourceSet resourceSet
;
1232 /** URI that needs to be loaded as an EMF model. */
1233 private final URI uri
;
1235 /** Monitor on which to report progress to the user. */
1236 private final ThreadSafeProgressMonitor monitor
;
1239 * Constructs a resolver to load a resource from its URI.
1241 * @param resourceSet
1242 * The resource set in which to load the resource.
1244 * The URI that needs to be loaded as an EMF model.
1246 * Monitor on which to report progress to the user.
1248 public RemoteResourceResolver(SynchronizedResourceSet resourceSet
, URI uri
,
1249 ThreadSafeProgressMonitor monitor
) {
1250 this.resourceSet
= resourceSet
;
1252 this.monitor
= monitor
;
1255 /** {@inheritDoc} */
1257 if (isInterruptedOrCanceled(monitor
)) {
1258 demandResolvingAndUnloadingPoolShutdown();
1262 final Resource resource
= resourceSet
.loadResource(uri
);
1263 Diagnostic resourceDiagnostic
= EcoreUtil
.computeDiagnostic(resource
, true);
1264 if (resourceDiagnostic
.getSeverity() >= Diagnostic
.WARNING
) {
1265 safeMergeDiagnostic(resourceDiagnostic
);
1267 if (getResolutionScope() != CrossReferenceResolutionScope
.SELF
) {
1268 final Set
<URI
> crossReferencedResources
= resourceSet
.discoverCrossReferences(resource
,
1270 for (URI crossRef
: crossReferencedResources
) {
1271 if (isInterruptedOrCanceled(monitor
)) {
1272 demandResolvingAndUnloadingPoolShutdown();
1273 // do not return, we want to unload what we've already loaded to avoid leaks.
1276 demandRemoteResolve(resourceSet
, crossRef
, monitor
);
1279 demandUnload(resourceSet
, resource
, monitor
);
1284 * Implementation of a Runnable that can be used to unload a given resource and make it
1285 * garbage-collectable.
1287 * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
1289 private static class ResourceUnloader
implements Runnable
{
1290 /** The resource set from which to unload a resource. */
1291 private final SynchronizedResourceSet resourceSet
;
1293 /** The resource to unload. */
1294 private final Resource resource
;
1296 /** Monitor on which to report progress to the user. */
1297 private final IProgressMonitor monitor
;
1300 * Default constructor.
1302 * @param resourceSet
1303 * The resource set from which to unload a resource.
1305 * The resource to unload.
1307 * Monitor on which to report progress to the user.
1309 public ResourceUnloader(SynchronizedResourceSet resourceSet
, Resource resource
,
1310 IProgressMonitor monitor
) {
1311 this.resourceSet
= resourceSet
;
1312 this.resource
= resource
;
1313 this.monitor
= monitor
;
1316 /** {@inheritDoc} */
1318 resourceSet
.unload(resource
, monitor
);
1323 * This implementation of a resource visitor will allow us to browse a given hierarchy and resolve the
1324 * models files in contains, as determined by {@link ThreadedModelResolver#MODEL_CONTENT_TYPES}.
1326 * @author <a href="mailto:laurent.goubet@obeo.fr">laurent Goubet</a>
1327 * @see ThreadedModelResolver#hasModelType(IFile)
1329 private class ModelResourceVisitor
implements IResourceVisitor
{
1330 /** Resource set in which to load the model files this visitor will find. */
1331 private final SynchronizedResourceSet resourceSet
;
1333 /** Monitor on which to report progress to the user. */
1334 private final ThreadSafeProgressMonitor monitor
;
1337 * Default constructor.
1339 * @param resourceSet
1340 * The resource set in which this visitor will load the model files it finds.
1342 * Monitor on which to report progress to the user.
1344 public ModelResourceVisitor(SynchronizedResourceSet resourceSet
, ThreadSafeProgressMonitor monitor
) {
1345 this.resourceSet
= resourceSet
;
1346 this.monitor
= monitor
;
1349 /** {@inheritDoc} */
1350 public boolean visit(IResource resource
) throws CoreException
{
1351 if (isInterruptedOrCanceled(monitor
)) {
1352 demandResolvingAndUnloadingPoolShutdown();
1354 throw new OperationCanceledException();
1357 if (resource
instanceof IFile
) {
1358 final IFile file
= (IFile
)resource
;
1359 if (hasModelType(file
)) {
1360 final URI expectedURI
= ResourceUtil
.createURIFor(file
);
1361 demandResolve(resourceSet
, expectedURI
, monitor
);
1370 * This will listen to workspace changes and react to all changes on "model" resources as determined by
1371 * {@link ThreadedModelResolver#MODEL_CONTENT_TYPES}.
1373 * @author <a href="mailto:laurent.goubet@obeo.fr">laurent Goubet</a>
1374 * @see ThreadedModelResolver#hasModelType(IFile)
1376 private static class ModelResourceListener
implements IResourceChangeListener
{
1377 /** Keeps track of the URIs that need to be reparsed when next we need the dependencies graph . */
1378 protected final Set
<URI
> changedURIs
;
1380 /** Tracks the files that have been removed. */
1381 protected final Set
<URI
> removedURIs
;
1383 /** Prevents concurrent access to the two internal sets. */
1384 protected final ReentrantLock internalLock
;
1386 /** Initializes this listener. */
1387 public ModelResourceListener() {
1388 this.changedURIs
= new LinkedHashSet
<URI
>();
1389 this.removedURIs
= new LinkedHashSet
<URI
>();
1390 this.internalLock
= new ReentrantLock();
1393 /** {@inheritDoc} */
1394 public void resourceChanged(IResourceChangeEvent event
) {
1395 final IResourceDelta delta
= event
.getDelta();
1396 if (delta
== null) {
1401 * We must block any and all threads from using the two internal sets through either
1402 * popChangedURIs or popRemovedURIs while we are parsing a resource delta. This particular locking
1403 * is here to avoid such misuses.
1405 internalLock
.lock();
1407 delta
.accept(new ModelResourceDeltaVisitor());
1408 } catch (CoreException e
) {
1409 EMFCompareIDEUIPlugin
.getDefault().log(e
);
1411 internalLock
.unlock();
1416 * Retrieves the set of all changed URIs since we last updated the dependencies graph, and clears it
1417 * for subsequent calls.
1419 * @return The set of all changed URIs since we last updated the dependencies graph.
1421 public Set
<URI
> popChangedURIs() {
1422 final Set
<URI
> changed
;
1423 internalLock
.lock();
1425 changed
= ImmutableSet
.copyOf(changedURIs
);
1426 changedURIs
.clear();
1428 internalLock
.unlock();
1434 * Retrieves the set of all removed URIs since we last updated the dependencies graph, and clears it
1435 * for subsequent calls.
1437 * @return The set of all removed URIs since we last updated the dependencies graph.
1439 public Set
<URI
> popRemovedURIs() {
1440 final Set
<URI
> removed
;
1441 internalLock
.lock();
1443 removed
= ImmutableSet
.copyOf(removedURIs
);
1444 removedURIs
.clear();
1446 internalLock
.unlock();
1452 * Visits a resource delta to collect the changed and removed files' URIs.
1454 * @author <a href="mailto:laurent.goubet@obeo.fr">laurent Goubet</a>
1456 private class ModelResourceDeltaVisitor
implements IResourceDeltaVisitor
{
1460 * @see org.eclipse.core.resources.IResourceDeltaVisitor#visit(org.eclipse.core.resources.IResourceDelta)
1462 public boolean visit(IResourceDelta delta
) throws CoreException
{
1464 * Note : the lock (#lock) must be acquired by the current thread _before_ calling #accept()
1467 if (delta
.getFlags() == IResourceDelta
.MARKERS
1468 || delta
.getResource().getType() != IResource
.FILE
) {
1472 final IFile file
= (IFile
)delta
.getResource();
1473 final URI fileURI
= createURIFor(file
);
1474 // We can't check the content type of a removed resource
1475 if (delta
.getKind() == IResourceDelta
.REMOVED
) {
1476 removedURIs
.add(fileURI
);
1477 changedURIs
.remove(fileURI
);
1478 } else if (hasModelType(file
)) {
1479 if ((delta
.getKind() & IResourceDelta
.CHANGED
) != 0) {
1480 changedURIs
.add(fileURI
);
1481 // Probably can't happen, but let's stay on the safe side
1482 removedURIs
.remove(fileURI
);
1483 } else if ((delta
.getKind() & IResourceDelta
.ADDED
) != 0) {
1484 // If a previously removed resource is changed, we can assume it's been re-added
1485 if (removedURIs
.remove(fileURI
)) {
1486 changedURIs
.add(fileURI
);