KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > core > internal > resources > AliasManager


1 /*******************************************************************************
2  * Copyright (c) 2000, 2007 IBM Corporation and others.
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  * IBM Corporation - initial API and implementation
10  * manklu@web.de - fix for bug 156082
11  * Bert Vingerhoets - fix for bug 169975
12  *******************************************************************************/

13 package org.eclipse.core.internal.resources;
14
15 import java.net.URI JavaDoc;
16 import java.util.*;
17 import org.eclipse.core.filesystem.EFS;
18 import org.eclipse.core.filesystem.IFileStore;
19 import org.eclipse.core.internal.events.ILifecycleListener;
20 import org.eclipse.core.internal.events.LifecycleEvent;
21 import org.eclipse.core.internal.localstore.FileSystemResourceManager;
22 import org.eclipse.core.internal.utils.Messages;
23 import org.eclipse.core.resources.*;
24 import org.eclipse.core.runtime.*;
25 import org.eclipse.osgi.util.NLS;
26
27 /**
28  * An alias is a resource that occupies the same file system location as another
29  * resource in the workspace. When a resource is modified in a way that affects
30  * the file on disk, all aliases need to be updated. This class is used to
31  * maintain data structures for quickly computing the set of aliases for a given
32  * resource, and for efficiently updating all aliases when a resource changes on
33  * disk.
34  *
35  * The approach for computing aliases is optimized for alias-free workspaces and
36  * alias-free projects. That is, if the workspace contains no aliases, then
37  * updating should be very quick. If a resource is changed in a project that
38  * contains no aliases, it should also be very fast.
39  *
40  * The data structures maintained by the alias manager can be seen as a cache,
41  * that is, they store no information that cannot be recomputed from other
42  * available information. On shutdown, the alias manager discards all state; on
43  * startup, the alias manager eagerly rebuilds its state. The reasoning is
44  * that it's better to incur this cost on startup than on the first attempt to
45  * modify a resource. After startup, the state is updated incrementally on the
46  * following occasions:
47  * - when projects are deleted, opened, closed, or moved
48  * - when linked resources are created, deleted, or moved.
49  */

50 public class AliasManager implements IManager, ILifecycleListener, IResourceChangeListener {
51     public class AddToCollectionDoit implements Doit {
52         Collection collection;
53
54         public void doit(IResource resource) {
55             collection.add(resource);
56         }
57
58         public void setCollection(Collection collection) {
59             this.collection = collection;
60         }
61     }
62
63     interface Doit {
64         public void doit(IResource resource);
65     }
66
67     class FindAliasesDoit implements Doit {
68         private int aliasType;
69         private IPath searchPath;
70
71         public void doit(IResource match) {
72             //don't record the resource we're computing aliases against as a match
73
if (match.getFullPath().isPrefixOf(searchPath))
74                 return;
75             IPath aliasPath = null;
76             switch (match.getType()) {
77                 case IResource.PROJECT :
78                     //first check if there is a linked resource that blocks the project location
79
if (suffix.segmentCount() > 0) {
80                         IResource testResource = ((IProject) match).findMember(suffix.segment(0));
81                         if (testResource != null && testResource.isLinked())
82                             return;
83                     }
84                     //there is an alias under this project
85
aliasPath = match.getFullPath().append(suffix);
86                     break;
87                 case IResource.FOLDER :
88                     aliasPath = match.getFullPath().append(suffix);
89                     break;
90                 case IResource.FILE :
91                     if (suffix.segmentCount() == 0)
92                         aliasPath = match.getFullPath();
93                     break;
94             }
95             if (aliasPath != null)
96                 if (aliasType == IResource.FILE) {
97                     aliases.add(workspace.getRoot().getFile(aliasPath));
98                 } else {
99                     if (aliasPath.segmentCount() == 1)
100                         aliases.add(workspace.getRoot().getProject(aliasPath.lastSegment()));
101                     else
102                         aliases.add(workspace.getRoot().getFolder(aliasPath));
103                 }
104         }
105
106         /**
107          * Sets the resource that we are searching for aliases for.
108          */

109         public void setSearchAlias(IResource aliasResource) {
110             this.aliasType = aliasResource.getType();
111             this.searchPath = aliasResource.getFullPath();
112         }
113     }
114
115     /**
116      * Maintains a mapping of FileStore->IResource, such that multiple resources
117      * mapped from the same location are tolerated.
118      */

119     class LocationMap {
120         /**
121          * Map of FileStore->IResource OR FileStore->ArrayList of (IResource)
122          */

123         private final SortedMap map = new TreeMap(getComparator());
124
125         /**
126          * Adds the given resource to the map, keyed by the given location.
127          * Returns true if a new entry was added, and false otherwise.
128          */

129         public boolean add(IFileStore location, IResource resource) {
130             Object JavaDoc oldValue = map.get(location);
131             if (oldValue == null) {
132                 map.put(location, resource);
133                 return true;
134             }
135             if (oldValue instanceof IResource) {
136                 if (resource.equals(oldValue))
137                     return false;//duplicate
138
ArrayList newValue = new ArrayList(2);
139                 newValue.add(oldValue);
140                 newValue.add(resource);
141                 map.put(location, newValue);
142                 return true;
143             }
144             ArrayList list = (ArrayList) oldValue;
145             if (list.contains(resource))
146                 return false;//duplicate
147
list.add(resource);
148             return true;
149         }
150
151         /**
152          * Method clear.
153          */

154         public void clear() {
155             map.clear();
156         }
157
158         /**
159          * Invoke the given doit for every resource whose location has the
160          * given location as a prefix.
161          */

162         public void matchingPrefixDo(IFileStore prefix, Doit doit) {
163             SortedMap matching;
164             IFileStore prefixParent = prefix.getParent();
165             if (prefixParent != null) {
166                 //endPoint is the smallest possible path greater than the prefix that doesn't
167
//match the prefix
168
IFileStore endPoint = prefixParent.getChild(prefix.getName() + "\0"); //$NON-NLS-1$
169
matching = map.subMap(prefix, endPoint);
170             } else {
171                 matching = map;
172             }
173             for (Iterator it = matching.values().iterator(); it.hasNext();) {
174                 Object JavaDoc value = it.next();
175                 if (value == null)
176                     return;
177                 if (value instanceof List) {
178                     Iterator duplicates = ((List) value).iterator();
179                     while (duplicates.hasNext())
180                         doit.doit((IResource) duplicates.next());
181                 } else {
182                     doit.doit((IResource) value);
183                 }
184             }
185         }
186
187         /**
188          * Invoke the given doit for every resource that matches the given
189          * location.
190          */

191         public void matchingResourcesDo(IFileStore location, Doit doit) {
192             Object JavaDoc value = map.get(location);
193             if (value == null)
194                 return;
195             if (value instanceof List) {
196                 Iterator duplicates = ((List) value).iterator();
197                 while (duplicates.hasNext())
198                     doit.doit((IResource) duplicates.next());
199             } else {
200                 doit.doit((IResource) value);
201             }
202         }
203
204         /**
205          * Calls the given doit with the project of every resource in the map
206          * whose location overlaps another resource in the map.
207          */

208         public void overLappingResourcesDo(Doit doit) {
209             Iterator entries = map.entrySet().iterator();
210             IFileStore previousStore = null;
211             IResource previousResource = null;
212             while (entries.hasNext()) {
213                 Map.Entry current = (Map.Entry) entries.next();
214                 //value is either single resource or List of resources
215
IFileStore currentStore = (IFileStore) current.getKey();
216                 IResource currentResource = null;
217                 Object JavaDoc value = current.getValue();
218                 if (value instanceof List) {
219                     //if there are several then they're all overlapping
220
Iterator duplicates = ((List) value).iterator();
221                     while (duplicates.hasNext())
222                         doit.doit(((IResource) duplicates.next()).getProject());
223                 } else {
224                     //value is a single resource
225
currentResource = (IResource) value;
226                 }
227                 if (previousStore != null) {
228                     //check for overlap with previous
229
//Note: previous is always shorter due to map sorting rules
230
if (previousStore.isParentOf(currentStore)) {
231                         //resources will be null if they were in a list, in which case
232
//they've already been passed to the doit
233
if (previousResource != null) {
234                             doit.doit(previousResource.getProject());
235                             //null out previous resource so we don't call doit twice with same resource
236
previousResource = null;
237                         }
238                         if (currentResource != null)
239                             doit.doit(currentResource.getProject());
240                         //keep iterating with the same previous store because there may be more overlaps
241
continue;
242                     }
243                 }
244                 previousStore = currentStore;
245                 previousResource = currentResource;
246             }
247         }
248
249         /**
250          * Removes the given location from the map. Returns true if anything
251          * was actually removed, and false otherwise.
252          */

253         public boolean remove(IFileStore location, IResource resource) {
254             Object JavaDoc oldValue = map.get(location);
255             if (oldValue == null)
256                 return false;
257             if (oldValue instanceof IResource) {
258                 if (resource.equals(oldValue)) {
259                     map.remove(location);
260                     return true;
261                 }
262                 return false;
263             }
264             ArrayList list = (ArrayList) oldValue;
265             boolean wasRemoved = list.remove(resource);
266             if (list.size() == 0)
267                 map.remove(location);
268             return wasRemoved;
269         }
270     }
271
272     /**
273      * Doit convenience class for adding items to a list
274      */

275     private final AddToCollectionDoit addToCollection = new AddToCollectionDoit();
276
277     /**
278      * The set of IProjects that have aliases.
279      */

280     protected final Set aliasedProjects = new HashSet();
281
282     /**
283      * A temporary set of aliases. Used during computeAliases, but maintained
284      * as a field as an optimization to prevent recreating the set.
285      */

286     protected final HashSet aliases = new HashSet();
287
288     /**
289      * The set of resources that have had structure changes that might
290      * invalidate the locations map or aliased projects set. These will be
291      * updated incrementally on the next alias request.
292      */

293     private final Set changedLinks = new HashSet();
294
295     /**
296      * This flag is true when projects have been created or deleted and the
297      * location map has not been updated accordingly.
298      */

299     private boolean changedProjects = false;
300
301     /**
302      * The Doit class used for finding aliases.
303      */

304     private final FindAliasesDoit findAliases = new FindAliasesDoit();
305
306     /**
307      * This maps IFileStore ->IResource, associating a file system location
308      * with the projects and/or linked resources that are rooted at that location.
309      */

310     protected final LocationMap locationsMap = new LocationMap();
311     /**
312      * The total number of resources in the workspace that are not in the default
313      * location. This includes all linked resources, including linked resources
314      * that don't currently have valid locations due to an undefined path variable.
315      * This also includes projects that are not in their default location.
316      * This value is used as a quick optimization, because a workspace with
317      * all resources in their default locations cannot have any aliases.
318      */

319     private int nonDefaultResourceCount = 0;
320
321     /**
322      * The suffix object is also used only during the computeAliases method.
323      * In this case it is a field because it is referenced from an inner class
324      * and we want to avoid creating a pointer array. It is public to eliminate
325      * the need for synthetic accessor methods.
326      */

327     public IPath suffix;
328
329     /** the workspace */
330     protected final Workspace workspace;
331
332     public AliasManager(Workspace workspace) {
333         this.workspace = workspace;
334     }
335
336     private void addToLocationsMap(IProject project) {
337         IFileStore location = ((Resource) project).getStore();
338         if (location != null)
339             locationsMap.add(location, project);
340         ProjectDescription description = ((Project) project).internalGetDescription();
341         if (description == null)
342             return;
343         if (description.getLocationURI() != null)
344             nonDefaultResourceCount++;
345         HashMap links = description.getLinks();
346         if (links == null)
347             return;
348         for (Iterator it = links.values().iterator(); it.hasNext();) {
349             LinkDescription linkDesc = (LinkDescription) it.next();
350             IResource link = project.findMember(linkDesc.getProjectRelativePath());
351             if (link != null) {
352                 try {
353                     addToLocationsMap(link, EFS.getStore(linkDesc.getLocationURI()));
354                 } catch (CoreException e) {
355                     //ignore links with invalid locations
356
}
357             }
358         }
359     }
360
361     private void addToLocationsMap(IResource link, IFileStore location) {
362         if (location != null)
363             if (locationsMap.add(location, link))
364                 nonDefaultResourceCount++;
365     }
366
367     /**
368      * Builds the table of aliased projects from scratch.
369      */

370     private void buildAliasedProjectsSet() {
371         aliasedProjects.clear();
372         //if there are no resources in non-default locations then there can't be any aliased projects
373
if (nonDefaultResourceCount <= 0)
374             return;
375         //for every resource that overlaps another, marked its project as aliased
376
addToCollection.setCollection(aliasedProjects);
377         locationsMap.overLappingResourcesDo(addToCollection);
378     }
379
380     /**
381      * Builds the table of resource locations from scratch. Also computes an
382      * initial value for the linked resource counter.
383      */

384     private void buildLocationsMap() {
385         locationsMap.clear();
386         nonDefaultResourceCount = 0;
387         //build table of IPath (file system location) -> IResource (project or linked resource)
388
IProject[] projects = workspace.getRoot().getProjects();
389         for (int i = 0; i < projects.length; i++)
390             if (projects[i].isAccessible())
391                 addToLocationsMap(projects[i]);
392     }
393
394     /**
395      * A project alias needs updating. If the project location has been deleted,
396      * then the project should be deleted from the workspace. This differs
397      * from the refresh local strategy, but operations performed from within
398      * the workspace must never leave a resource out of sync.
399      * @param project The project to check for deletion
400      * @param location The project location
401      * @return <code>true</code> if the project has been deleted, and <code>false</code> otherwise
402      * @exception CoreException
403      */

404     private boolean checkDeletion(Project project, IFileStore location) throws CoreException {
405         if (project.exists() && !location.fetchInfo().exists()) {
406             //perform internal deletion of project from workspace tree because
407
// it is already deleted from disk and we can't acquire a different
408
//scheduling rule in this context (none is needed because we are
409
//within scope of the workspace lock)
410
Assert.isTrue(workspace.getWorkManager().getLock().getDepth() > 0);
411             project.deleteResource(false, null);
412             return true;
413         }
414         return false;
415     }
416
417     /**
418      * Returns all aliases of the given resource, or null if there are none.
419      */

420     public IResource[] computeAliases(final IResource resource, IFileStore location) {
421         //nothing to do if we are or were in an alias-free workspace or project
422
if (hasNoAliases(resource))
423             return null;
424
425         aliases.clear();
426         internalComputeAliases(resource, location);
427         int size = aliases.size();
428         if (size == 0)
429             return null;
430         return (IResource[]) aliases.toArray(new IResource[size]);
431     }
432
433     /**
434      * Returns all aliases of this resource, and any aliases of subtrees of this
435      * resource. Returns null if no aliases are found.
436      */

437     private void computeDeepAliases(IResource resource, IFileStore location) {
438         //if the location is invalid then there won't be any aliases to update
439
if (location == null)
440             return;
441         //get the normal aliases (resources rooted in parent locations)
442
internalComputeAliases(resource, location);
443         //get all resources rooted below this resource's location
444
addToCollection.setCollection(aliases);
445         locationsMap.matchingPrefixDo(location, addToCollection);
446         //if this is a project, get all resources rooted below links in this project
447
if (resource.getType() == IResource.PROJECT) {
448             try {
449                 IResource[] members = ((IProject) resource).members();
450                 final FileSystemResourceManager localManager = workspace.getFileSystemManager();
451                 for (int i = 0; i < members.length; i++) {
452                     if (members[i].isLinked()) {
453                         IFileStore linkLocation = localManager.getStore(members[i]);
454                         if (linkLocation != null)
455                             locationsMap.matchingPrefixDo(linkLocation, addToCollection);
456                     }
457                 }
458             } catch (CoreException e) {
459                 //skip inaccessible projects
460
}
461         }
462     }
463
464     /**
465      * Returns the comparator to use when sorting the locations map. Comparison
466      * is based on segments, so that paths with the most segments in common will
467      * always be adjacent. This is equivalent to the natural order on the path
468      * strings, with the extra condition that the path separator is ordered
469      * before all other characters. (Ex: "/foo" < "/foo/zzz" < "/fooaaa").
470      */

471     private Comparator getComparator() {
472         return new Comparator() {
473             public int compare(Object JavaDoc o1, Object JavaDoc o2) {
474                 IFileStore store1 = (IFileStore) o1;
475                 IFileStore store2 = (IFileStore) o2;
476                 //scheme takes precedence over all else
477
int compare = compareStringOrNull(store1.getFileSystem().getScheme(), store2.getFileSystem().getScheme());
478                 if (compare != 0)
479                     return compare;
480                 // compare based on URI path segment values
481
final URI JavaDoc uri1 = store1.toURI();
482                 final URI JavaDoc uri2 = store2.toURI();
483                 IPath path1 = new Path(uri1.getPath());
484                 IPath path2 = new Path(uri2.getPath());
485                 int segmentCount1 = path1.segmentCount();
486                 int segmentCount2 = path2.segmentCount();
487                 for (int i = 0; (i < segmentCount1) && (i < segmentCount2); i++) {
488                     compare = path1.segment(i).compareTo(path2.segment(i));
489                     if (compare != 0)
490                         return compare;
491                 }
492                 //all segments are equal, so compare based on number of segments
493
compare = segmentCount1 - segmentCount2;
494                 if (compare != 0)
495                     return compare;
496                 //same number of segments, so compare query
497
return compareStringOrNull(uri1.getQuery(), uri2.getQuery());
498             }
499
500             /**
501              * Compares two strings that are possibly null.
502              */

503             private int compareStringOrNull(String JavaDoc string1, String JavaDoc string2) {
504                 if (string1 == null) {
505                     if (string2 == null)
506                         return 0;
507                     return 1;
508                 }
509                 if (string2 == null)
510                     return -1;
511                 return string1.compareTo(string2);
512
513             }
514         };
515     }
516
517     public void handleEvent(LifecycleEvent event) {
518         /*
519          * We can't determine the end state for most operations because they may
520          * fail after we receive pre-notification. In these cases, we remember
521          * the invalidated resources and recompute their state lazily on the
522          * next alias request.
523          */

524         switch (event.kind) {
525             case LifecycleEvent.PRE_LINK_DELETE :
526                 Resource link = (Resource) event.resource;
527                 if (link.isLinked())
528                     removeFromLocationsMap(link, link.getStore());
529                 //fall through
530
case LifecycleEvent.PRE_LINK_CREATE :
531                 changedLinks.add(event.resource);
532                 break;
533             case LifecycleEvent.PRE_LINK_COPY :
534                 changedLinks.add(event.newResource);
535                 break;
536             case LifecycleEvent.PRE_LINK_MOVE :
537                 link = (Resource) event.resource;
538                 if (link.isLinked())
539                     removeFromLocationsMap(link, link.getStore());
540                 changedLinks.add(event.newResource);
541                 break;
542         }
543     }
544
545     /**
546      * Returns true if this resource is guaranteed to have no aliases, and false
547      * otherwise.
548      */

549     private boolean hasNoAliases(final IResource resource) {
550         //check if we're in an aliased project or workspace before updating structure changes. In the
551
//deletion case, we need to know if the resource was in an aliased project *before* deletion.
552
IProject project = resource.getProject();
553         boolean noAliases = !aliasedProjects.contains(project);
554
555         //now update any structure changes and check again if an update is needed
556
if (hasStructureChanges()) {
557             updateStructureChanges();
558             noAliases &= nonDefaultResourceCount <= 0 || !aliasedProjects.contains(project);
559         }
560         return noAliases;
561     }
562
563     /**
564      * Returns whether there are any structure changes that we have not yet processed.
565      */

566     private boolean hasStructureChanges() {
567         return changedProjects || !changedLinks.isEmpty();
568     }
569
570     /**
571      * Computes the aliases of the given resource at the given location, and
572      * adds them to the "aliases" collection.
573      */

574     private void internalComputeAliases(IResource resource, IFileStore location) {
575         IFileStore searchLocation = location;
576         if (searchLocation == null)
577             searchLocation = ((Resource) resource).getStore();
578         //if the location is invalid then there won't be any aliases to update
579
if (searchLocation == null)
580             return;
581
582         suffix = Path.EMPTY;
583         findAliases.setSearchAlias(resource);
584         /*
585          * Walk up the location segments for this resource, looking for a
586          * resource with a matching location. All matches are then added to the
587          * "aliases" set.
588          */

589         do {
590             locationsMap.matchingResourcesDo(searchLocation, findAliases);
591             suffix = new Path(searchLocation.getName()).append(suffix);
592             searchLocation = searchLocation.getParent();
593         } while (searchLocation != null);
594     }
595
596     private void removeFromLocationsMap(IResource link, IFileStore location) {
597         if (location != null)
598             if (locationsMap.remove(location, link))
599                 nonDefaultResourceCount--;
600     }
601
602     public void resourceChanged(IResourceChangeEvent event) {
603         final IResourceDelta delta = event.getDelta();
604         if (delta == null)
605             return;
606         //invalidate location map if there are added or removed projects.
607
if (delta.getAffectedChildren(IResourceDelta.ADDED | IResourceDelta.REMOVED).length > 0)
608             changedProjects = true;
609     }
610
611     /* (non-Javadoc)
612      * @see IManager#shutdown(IProgressMonitor)
613      */

614     public void shutdown(IProgressMonitor monitor) {
615         workspace.removeResourceChangeListener(this);
616         locationsMap.clear();
617     }
618
619     /* (non-Javadoc)
620      * @see IManager#startup(IProgressMonitor)
621      */

622     public void startup(IProgressMonitor monitor) {
623         workspace.addLifecycleListener(this);
624         workspace.addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
625         buildLocationsMap();
626         buildAliasedProjectsSet();
627     }
628
629     /**
630      * The file underlying the given resource has changed on disk. Compute all
631      * aliases for this resource and update them. This method will not attempt
632      * to incur any units of work on the given progress monitor, but it may
633      * update the subtask to reflect what aliases are being updated.
634      * @param resource the resource to compute aliases for
635      * @param location the file system location of the resource (passed as a
636      * parameter because in the project deletion case the resource is no longer
637      * accessible at time of update).
638      * @param depth whether to search for aliases on all children of the given
639      * resource. Only depth ZERO and INFINITE are used.
640      */

641     public void updateAliases(IResource resource, IFileStore location, int depth, IProgressMonitor monitor) throws CoreException {
642         if (hasNoAliases(resource))
643             return;
644         aliases.clear();
645         if (depth == IResource.DEPTH_ZERO)
646             internalComputeAliases(resource, location);
647         else
648             computeDeepAliases(resource, location);
649         if (aliases.size() == 0)
650             return;
651         FileSystemResourceManager localManager = workspace.getFileSystemManager();
652         for (Iterator it = aliases.iterator(); it.hasNext();) {
653             IResource alias = (IResource) it.next();
654             monitor.subTask(NLS.bind(Messages.links_updatingDuplicate, alias.getFullPath()));
655             if (alias.getType() == IResource.PROJECT) {
656                 if (checkDeletion((Project) alias, location))
657                     continue;
658                 //project did not require deletion, so fall through below and refresh it
659
}
660             localManager.refresh(alias, IResource.DEPTH_INFINITE, false, null);
661         }
662     }
663
664     /**
665      * Process any structural changes that have occurred since the last alias
666      * request.
667      */

668     private void updateStructureChanges() {
669         boolean hadChanges = false;
670         if (changedProjects) {
671             //if a project is added or removed, just recompute the whole world
672
changedProjects = false;
673             hadChanges = true;
674             buildLocationsMap();
675         } else {
676             //incrementally update location map for changed links
677
for (Iterator it = changedLinks.iterator(); it.hasNext();) {
678                 IResource resource = (IResource) it.next();
679                 hadChanges = true;
680                 if (!resource.isAccessible())
681                     continue;
682                 if (resource.isLinked())
683                     addToLocationsMap(resource, ((Resource) resource).getStore());
684             }
685         }
686         changedLinks.clear();
687         if (hadChanges)
688             buildAliasedProjectsSet();
689         changedProjects = false;
690     }
691 }
692
Popular Tags