Model resolving preferences
[EMFCompare2.git] / plugins / org.eclipse.emf.compare.ide.ui / src / org / eclipse / emf / compare / ide / ui / internal / logical / resolver / ThreadedModelResolver.java
blobd781e1be3a48bb9faa825fa31c124936e9380dc8
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
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.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;
35 import java.util.Set;
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;
79 /**
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.
83 * <p>
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.
89 * </p>
90 * <p>
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.
94 * </p>
95 * <p>
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.
98 * </p>
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) {
107 if (input != null) {
108 return createURIFor(input);
110 return null;
115 * Keeps track of the discovered dependency graph for local resources.
116 * <p>
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.
125 * </p>
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).
141 * <p>
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.
145 * </p>
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.
173 * <p>
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.
176 * </p>
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
185 * in sync.
186 * <p>
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.
190 * </p>
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$
212 .build();
213 this.resolvingPool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(
214 availableProcessors, resolvingThreadFactory));
215 ThreadFactory unloadingThreadFactory = new ThreadFactoryBuilder().setNameFormat(
216 "EMFCompare-UnloadingThread-%d") //$NON-NLS-1$
217 .build();
218 this.unloadingPool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(
219 availableProcessors, unloadingThreadFactory));
223 * {@inheritDoc}
225 @Override
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$
235 /** {@inheritDoc} */
236 @Override
237 public void dispose() {
238 terminator.shutdown();
239 ResourcesPlugin.getWorkspace().removeResourceChangeListener(resourceListener);
240 super.dispose();
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.
260 * <p>
261 * Copy/pasted from {@link ExecutorService} javadoc.
263 * @param pool
264 * the pool to shutdown
265 * @return true if the pool has been properly shutdown, false otherwise.
267 private static boolean shutdownAndAwaitTermination(ExecutorService pool) {
268 boolean ret = true;
269 pool.shutdown(); // Disable new tasks from being submitted
270 try {
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)) {
276 ret = false;
279 } catch (InterruptedException ie) {
280 // (Re-)Cancel if current thread also interrupted
281 pool.shutdownNow();
282 // Preserve interrupt status
283 Thread.currentThread().interrupt();
284 ret = false;
286 return ret;
289 /** {@inheritDoc} */
290 public boolean canResolve(IStorage sourceStorage) {
291 return true;
295 * {@inheritDoc}
296 * <p>
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.
301 * </p>
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();
311 try {
312 subMonitor = new ThreadSafeProgressMonitor(SubMonitor.convert(monitor, 100));
313 while (!currentlyResolving.isEmpty()) {
314 notResolving.await();
317 setupResolving();
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);
336 return traversal;
337 } finally {
338 try {
339 finalizeResolving();
341 if (subMonitor != null) {
342 subMonitor.setWorkRemaining(0);
344 } finally {
345 notResolving.signal();
346 lock.unlock();
352 * {@inheritDoc}
353 * <p>
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.
358 * </p>
360 public SynchronizationModel resolveLocalModels(IResource left, IResource right, IResource origin,
361 IProgressMonitor monitor) throws InterruptedException {
363 ThreadSafeProgressMonitor subMonitor = new ThreadSafeProgressMonitor(SubMonitor.convert(monitor, 100));
364 try {
365 if (!(left instanceof IFile && right instanceof IFile && (origin == null || origin instanceof IFile))) {
366 return resolveNonFileLocalModels(left, right, origin, subMonitor);
367 } else {
368 return resolveFileLocalModel(left, right, origin, subMonitor);
370 } finally {
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);
383 } else {
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();
394 try {
395 while (!currentlyResolving.isEmpty()) {
396 notResolving.await();
399 setupResolving();
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);
413 final URI originURI;
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);
418 } else {
419 startingPoints = ImmutableSet.of((IFile)left, (IFile)right);
420 originURI = null;
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));
438 } else {
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,
457 intersection));
458 final Set<IStorage> actualRight = new LinkedHashSet<IStorage>(difference(rightTraversal,
459 intersection));
460 final Set<IStorage> actualOrigin = new LinkedHashSet<IStorage>(difference(originTraversal,
461 intersection));
462 final SynchronizationModel synchronizationModel = new SynchronizationModel(new StorageTraversal(
463 actualLeft), new StorageTraversal(actualRight), new StorageTraversal(actualOrigin),
464 diagnostic);
466 return synchronizationModel;
467 } finally {
468 try {
469 finalizeResolving();
470 } finally {
471 notResolving.signal();
472 lock.unlock();
478 * {@inheritDoc}
479 * <p>
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.
484 * </p>
486 public SynchronizationModel resolveModels(IStorageProviderAccessor storageAccessor, IStorage left,
487 IStorage right, IStorage origin, IProgressMonitor monitor) throws InterruptedException {
489 ThreadSafeProgressMonitor subMonitor = null;
490 lock.lockInterruptibly();
491 try {
492 subMonitor = new ThreadSafeProgressMonitor(SubMonitor.convert(monitor, 100));
494 while (!currentlyResolving.isEmpty()) {
495 notResolving.await();
498 setupResolving();
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,
509 subMonitor);
510 } else {
511 synchronizationModel = resolveRemoteModels(storageAccessor, left, right, origin, subMonitor);
514 return synchronizationModel;
515 } finally {
516 try {
517 finalizeResolving();
519 if (subMonitor != null) {
520 subMonitor.setWorkRemaining(0);
522 } finally {
523 notResolving.signal();
524 lock.unlock();
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() {
534 createThreadPools();
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()) {
545 shutdownPools();
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;
555 diagnostic = 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.
568 * @param left
569 * File corresponding to the left side of this comparison.
570 * @param right
571 * "starting point" of the traversal to resolve as the right logical model.
572 * @param origin
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>.
575 * @param monitor
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,
606 monitor);
607 } else {
608 originTraversal = Collections.emptySet();
611 final SynchronizationModel synchronizationModel = new SynchronizationModel(new StorageTraversal(
612 leftTraversal), new StorageTraversal(rightTraversal), new StorageTraversal(originTraversal),
613 diagnostic);
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.
624 * @param left
625 * "starting point" of the traversal to resolve as the left logical model.
626 * @param right
627 * "starting point" of the traversal to resolve as the right logical model.
628 * @param origin
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>.
631 * @param monitor
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);
648 } else {
649 originTraversal = Collections.emptySet();
651 final SynchronizationModel synchronizationModel = new SynchronizationModel(new StorageTraversal(
652 leftTraversal), new StorageTraversal(rightTraversal), new StorageTraversal(originTraversal),
653 diagnostic);
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.
662 * @param resourceSet
663 * The resource set in which to load our temporary resources.
664 * @param monitor
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.
689 * <p>
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}.
692 * </p>
694 * @param resourceSet
695 * The resource set in which to load the temporary resources.
696 * @param file
697 * The file which we need to be present in the dependency graph.
698 * @param monitor
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);
707 try {
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()}.
719 * @param file
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()) {
727 case WORKSPACE:
728 startingPoint = ResourcesPlugin.getWorkspace().getRoot();
729 break;
730 case PROJECT:
731 startingPoint = file.getProject();
732 break;
733 case CONTAINER:
734 startingPoint = file.getParent();
735 break;
736 case OUTGOING:
737 // fall through, the difference between SELF and OUTGOING will only come later on
738 case SELF:
739 // fall through
740 default:
741 startingPoint = file;
742 break;
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));
770 return traversal;
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()) {
813 case WORKSPACE:
814 dependencies = dependencyGraph.getSubgraphContaining(expectedURI, bounds);
815 break;
816 case PROJECT:
817 final Set<URI> allDependencies = dependencyGraph.getSubgraphContaining(expectedURI, bounds);
818 final IResource project = file.getProject();
819 dependencies = Iterables.filter(allDependencies, isInContainer(project));
820 break;
821 case CONTAINER:
822 final Set<URI> allDependencies1 = dependencyGraph.getSubgraphContaining(expectedURI, bounds);
823 final IResource container = file.getParent();
824 dependencies = Iterables.filter(allDependencies1, isInContainer(container));
825 break;
826 case OUTGOING:
827 dependencies = dependencyGraph.getTreeFrom(expectedURI, bounds);
828 break;
829 case SELF:
830 // fall through
831 default:
832 dependencies = Collections.singleton(expectedURI);
833 break;
835 return dependencies;
839 * Returns the IFile located at the given URI.
841 * @param 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
859 * given container.
861 * @param container
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) {
868 if (input != null) {
869 final IFile pointedFile = getFileAt(input);
870 if (pointedFile != null) {
871 return container.getLocation().isPrefixOf(pointedFile.getLocation());
874 return false;
880 * When executing local comparisons, we resolve the full logical model of both (or "all three of") the
881 * compared files.
882 * <p>
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
887 * compared ones.
888 * </p>
889 * <p>
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.
892 * </p>
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.
920 * <p>
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.
923 * </p>
925 * @param resourceSet
926 * The resource set in which to load the resource.
927 * @param uri
928 * The uri we are to try and load as a model.
929 * @param monitor
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();
937 return;
940 lock.lock();
941 try {
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,
947 monitor));
948 Futures.addCallback(future, new ResolvingFutureCallback(monitor, uri));
950 } finally {
951 lock.unlock();
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.
958 * <p>
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.
961 * </p>
963 * @param resourceSet
964 * The resource set in which to load the resource.
965 * @param uri
966 * The uri we are to try and load as a model.
967 * @param monitor
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();
974 return;
977 lock.lock();
978 try {
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,
984 uri, monitor));
985 Futures.addCallback(future, new ResolvingFutureCallback(monitor, uri));
987 } finally {
988 lock.unlock();
993 * Allows callers to launch the unloading of the given resource.
994 * <p>
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
998 * garbage collected.
999 * </p>
1001 * @param resourceSet
1002 * The resource set containing the resource to be unloaded.
1003 * @param resource
1004 * The resource to unload.
1005 * @param monitor
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)) {
1020 monitor.worked(1);
1024 public void onFailure(Throwable t) {
1025 if (!isInterruptedOrCanceled(monitor)) {
1026 monitor.worked(1);
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) {
1040 lock.lock();
1041 try {
1042 diagnostic.merge(resourceDiagnostic);
1043 } finally {
1044 lock.unlock();
1049 * Checks if the current thread is interrupted or if the given monitor has been canceled.
1051 * @param monitor
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() {
1068 public void run() {
1069 shutdownPools();
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
1091 * resolving tasks.
1093 * @param uri
1094 * the uri to remove.
1096 private void finalizeResolvingTask(URI uri) {
1097 lock.lock();
1098 try {
1099 currentlyResolving.remove(uri);
1100 if (currentlyResolving.isEmpty()) {
1101 resolutionEnd.signal();
1103 } finally {
1104 lock.unlock();
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
1111 * resolution.
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;
1123 * @param monitor
1125 private ResolvingFutureCallback(IProgressMonitor monitor, URI uri) {
1126 this.monitor = monitor;
1127 this.uri = uri;
1130 public void onSuccess(Object result) {
1131 try {
1132 if (!isInterruptedOrCanceled(monitor)) {
1133 // do not report progress anymore when the task has been interrupted of canceled. It
1134 // speeds up the cancellation.
1135 monitor.worked(1);
1137 } finally {
1138 finalizeResolvingTask(uri);
1142 public void onFailure(Throwable t) {
1143 try {
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.
1147 monitor.worked(1);
1148 safeMergeDiagnostic(BasicDiagnostic.toDiagnostic(t));
1150 } finally {
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.
1159 * <p>
1160 * Once done with the resolution, this thread will spawn an independent job to unload the resource.
1161 * </p>
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.
1180 * @param uri
1181 * URI that needs to be loaded as an EMF model.
1182 * @param monitor
1183 * Monitor on which to report progress to the user.
1185 public ResourceResolver(SynchronizedResourceSet resourceSet, URI uri,
1186 ThreadSafeProgressMonitor monitor) {
1187 this.resourceSet = resourceSet;
1188 this.uri = uri;
1189 this.monitor = monitor;
1192 /** {@inheritDoc} */
1193 public void run() {
1194 if (isInterruptedOrCanceled(monitor)) {
1195 demandResolvingAndUnloadingPoolShutdown();
1196 return;
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,
1207 monitor);
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.
1213 break;
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.
1243 * @param uri
1244 * The URI that needs to be loaded as an EMF model.
1245 * @param monitor
1246 * Monitor on which to report progress to the user.
1248 public RemoteResourceResolver(SynchronizedResourceSet resourceSet, URI uri,
1249 ThreadSafeProgressMonitor monitor) {
1250 this.resourceSet = resourceSet;
1251 this.uri = uri;
1252 this.monitor = monitor;
1255 /** {@inheritDoc} */
1256 public void run() {
1257 if (isInterruptedOrCanceled(monitor)) {
1258 demandResolvingAndUnloadingPoolShutdown();
1259 return;
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,
1269 monitor);
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.
1274 break;
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.
1304 * @param resource
1305 * The resource to unload.
1306 * @param monitor
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} */
1317 public void run() {
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.
1341 * @param monitor
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();
1353 // cancel the visit
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);
1363 return false;
1365 return true;
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) {
1397 return;
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();
1406 try {
1407 delta.accept(new ModelResourceDeltaVisitor());
1408 } catch (CoreException e) {
1409 EMFCompareIDEUIPlugin.getDefault().log(e);
1410 } finally {
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();
1424 try {
1425 changed = ImmutableSet.copyOf(changedURIs);
1426 changedURIs.clear();
1427 } finally {
1428 internalLock.unlock();
1430 return changed;
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();
1442 try {
1443 removed = ImmutableSet.copyOf(removedURIs);
1444 removedURIs.clear();
1445 } finally {
1446 internalLock.unlock();
1448 return removed;
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 {
1458 * {@inheritDoc}
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()
1465 * on this visitor.
1467 if (delta.getFlags() == IResourceDelta.MARKERS
1468 || delta.getResource().getType() != IResource.FILE) {
1469 return true;
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);
1491 return true;