| /******************************************************************************* |
| * Copyright (c) 2000, 2015 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * manklu@web.de - fix for bug 156082 |
| * Bert Vingerhoets - fix for bug 169975 |
| * Serge Beauchamp (Freescale Semiconductor) - [229633] Fix Concurency Exception |
| * Sergey Prigogin (Google) - [338010] Resource.createLink() does not preserve symbolic links |
| * Lars Vogel <Lars.Vogel@vogella.com> - Bug 473427 |
| *******************************************************************************/ |
| package org.eclipse.core.internal.resources; |
| |
| import java.net.URI; |
| import java.util.*; |
| import org.eclipse.core.filesystem.EFS; |
| import org.eclipse.core.filesystem.IFileStore; |
| import org.eclipse.core.internal.events.ILifecycleListener; |
| import org.eclipse.core.internal.events.LifecycleEvent; |
| import org.eclipse.core.internal.localstore.FileSystemResourceManager; |
| import org.eclipse.core.internal.utils.*; |
| import org.eclipse.core.resources.*; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.osgi.util.NLS; |
| |
| /** |
| * An alias is a resource that occupies the same file system location as another |
| * resource in the workspace. When a resource is modified in a way that affects |
| * the file on disk, all aliases need to be updated. This class is used to |
| * maintain data structures for quickly computing the set of aliases for a given |
| * resource, and for efficiently updating all aliases when a resource changes on |
| * disk. |
| * |
| * The approach for computing aliases is optimized for alias-free workspaces and |
| * alias-free projects. That is, if the workspace contains no aliases, then |
| * updating should be very quick. If a resource is changed in a project that |
| * contains no aliases, it should also be very fast. |
| * |
| * The data structures maintained by the alias manager can be seen as a cache, |
| * that is, they store no information that cannot be recomputed from other |
| * available information. On shutdown, the alias manager discards all state; on |
| * startup, the alias manager eagerly rebuilds its state. The reasoning is |
| * that it's better to incur this cost on startup than on the first attempt to |
| * modify a resource. After startup, the state is updated incrementally on the |
| * following occasions: |
| * - when projects are deleted, opened, closed, or moved |
| * - when linked resources are created, deleted, or moved. |
| */ |
| public class AliasManager implements IManager, ILifecycleListener, IResourceChangeListener { |
| public class AddToCollectionDoit implements Doit { |
| Collection<IResource> collection; |
| |
| @Override |
| public void doit(IResource resource) { |
| collection.add(resource); |
| } |
| |
| public void setCollection(Collection<IResource> collection) { |
| this.collection = collection; |
| } |
| } |
| |
| interface Doit { |
| void doit(IResource resource); |
| } |
| |
| class FindAliasesDoit implements Doit { |
| private int aliasType; |
| private IPath searchPath; |
| |
| @Override |
| public void doit(IResource match) { |
| //don't record the resource we're computing aliases against as a match |
| if (match.getFullPath().isPrefixOf(searchPath)) |
| return; |
| IPath aliasPath = null; |
| switch (match.getType()) { |
| case IResource.PROJECT : |
| //first check if there is a linked resource that blocks the project location |
| if (suffix.segmentCount() > 0) { |
| IResource testResource = ((IProject) match).findMember(suffix.segment(0)); |
| if (testResource != null && testResource.isLinked()) |
| return; |
| } |
| //there is an alias under this project |
| aliasPath = match.getFullPath().append(suffix); |
| break; |
| case IResource.FOLDER : |
| aliasPath = match.getFullPath().append(suffix); |
| break; |
| case IResource.FILE : |
| if (suffix.segmentCount() == 0) |
| aliasPath = match.getFullPath(); |
| break; |
| } |
| if (aliasPath != null) |
| if (aliasType == IResource.FILE) { |
| aliases.add(workspace.getRoot().getFile(aliasPath)); |
| } else { |
| if (aliasPath.segmentCount() == 1) |
| aliases.add(workspace.getRoot().getProject(aliasPath.lastSegment())); |
| else |
| aliases.add(workspace.getRoot().getFolder(aliasPath)); |
| } |
| } |
| |
| /** |
| * Sets the resource that we are searching for aliases for. |
| */ |
| public void setSearchAlias(IResource aliasResource) { |
| this.aliasType = aliasResource.getType(); |
| this.searchPath = aliasResource.getFullPath(); |
| } |
| } |
| |
| /** |
| * Maintains a mapping of FileStore->IResource, such that multiple resources |
| * mapped from the same location are tolerated. |
| */ |
| class LocationMap { |
| /** |
| * Map of FileStore->IResource OR FileStore->ArrayList of (IResource) |
| */ |
| private final SortedMap<IFileStore, Object> map = new TreeMap<>(getComparator()); |
| |
| /** |
| * Adds the given resource to the map, keyed by the given location. |
| * Returns true if a new entry was added, and false otherwise. |
| */ |
| public boolean add(IFileStore location, IResource resource) { |
| Object oldValue = map.get(location); |
| if (oldValue == null) { |
| map.put(location, resource); |
| return true; |
| } |
| if (oldValue instanceof IResource) { |
| if (resource.equals(oldValue)) |
| return false;//duplicate |
| ArrayList<Object> newValue = new ArrayList<>(2); |
| newValue.add(oldValue); |
| newValue.add(resource); |
| map.put(location, newValue); |
| return true; |
| } |
| @SuppressWarnings("unchecked") |
| ArrayList<IResource> list = (ArrayList<IResource>) oldValue; |
| if (list.contains(resource)) |
| return false;//duplicate |
| list.add(resource); |
| return true; |
| } |
| |
| /** |
| * Method clear. |
| */ |
| public void clear() { |
| map.clear(); |
| } |
| |
| /** |
| * Invoke the given doit for every resource whose location has the |
| * given location as a prefix. |
| */ |
| public void matchingPrefixDo(IFileStore prefix, Doit doit) { |
| SortedMap<IFileStore, Object> matching; |
| IFileStore prefixParent = prefix.getParent(); |
| if (prefixParent != null) { |
| //endPoint is the smallest possible path greater than the prefix that doesn't |
| //match the prefix |
| IFileStore endPoint = prefixParent.getChild(prefix.getName() + "\0"); //$NON-NLS-1$ |
| matching = map.subMap(prefix, endPoint); |
| } else { |
| matching = map; |
| } |
| for (Object value : matching.values()) { |
| if (value == null) |
| return; |
| if (value instanceof List) { |
| @SuppressWarnings("unchecked") |
| Iterator<IResource> duplicates = ((List<IResource>) value).iterator(); |
| while (duplicates.hasNext()) |
| doit.doit(duplicates.next()); |
| } else { |
| doit.doit((IResource) value); |
| } |
| } |
| } |
| |
| /** |
| * Invoke the given doit for every resource that matches the given |
| * location. |
| */ |
| public void matchingResourcesDo(IFileStore location, Doit doit) { |
| Object value = map.get(location); |
| if (value == null) |
| return; |
| if (value instanceof List) { |
| @SuppressWarnings("unchecked") |
| Iterator<IResource> duplicates = ((List<IResource>) value).iterator(); |
| while (duplicates.hasNext()) |
| doit.doit(duplicates.next()); |
| } else { |
| doit.doit((IResource) value); |
| } |
| } |
| |
| /** |
| * Calls the given doit with the project of every resource in the map |
| * whose location overlaps another resource in the map. |
| */ |
| public void overLappingResourcesDo(Doit doit) { |
| Iterator<Map.Entry<IFileStore, Object>> entries = map.entrySet().iterator(); |
| IFileStore previousStore = null; |
| IResource previousResource = null; |
| while (entries.hasNext()) { |
| Map.Entry<IFileStore, Object> current = entries.next(); |
| //value is either single resource or List of resources |
| IFileStore currentStore = current.getKey(); |
| IResource currentResource = null; |
| Object value = current.getValue(); |
| if (value instanceof List) { |
| //if there are several then they're all overlapping |
| @SuppressWarnings("unchecked") |
| Iterator<IResource> duplicates = ((List<IResource>) value).iterator(); |
| while (duplicates.hasNext()) |
| doit.doit(duplicates.next().getProject()); |
| } else { |
| //value is a single resource |
| currentResource = (IResource) value; |
| } |
| if (previousStore != null) { |
| //check for overlap with previous |
| //Note: previous is always shorter due to map sorting rules |
| if (previousStore.isParentOf(currentStore)) { |
| //resources will be null if they were in a list, in which case |
| //they've already been passed to the doit |
| if (previousResource != null) { |
| doit.doit(previousResource.getProject()); |
| //null out previous resource so we don't call doit twice with same resource |
| previousResource = null; |
| } |
| if (currentResource != null) |
| doit.doit(currentResource.getProject()); |
| //keep iterating with the same previous store because there may be more overlaps |
| continue; |
| } |
| } |
| previousStore = currentStore; |
| previousResource = currentResource; |
| } |
| } |
| |
| /** |
| * Removes the given location from the map. Returns true if anything |
| * was actually removed, and false otherwise. |
| */ |
| public boolean remove(IFileStore location, IResource resource) { |
| Object oldValue = map.get(location); |
| if (oldValue == null) |
| return false; |
| if (oldValue instanceof IResource) { |
| if (resource.equals(oldValue)) { |
| map.remove(location); |
| return true; |
| } |
| return false; |
| } |
| @SuppressWarnings("unchecked") |
| ArrayList<IResource> list = (ArrayList<IResource>) oldValue; |
| boolean wasRemoved = list.remove(resource); |
| if (list.isEmpty()) |
| map.remove(location); |
| return wasRemoved; |
| } |
| } |
| |
| /** |
| * Doit convenience class for adding items to a list |
| */ |
| private final AddToCollectionDoit addToCollection = new AddToCollectionDoit(); |
| |
| /** |
| * The set of IProjects that have aliases. |
| */ |
| protected final Set<IResource> aliasedProjects = new HashSet<>(); |
| |
| /** |
| * A temporary set of aliases. Used during computeAliases, but maintained |
| * as a field as an optimization to prevent recreating the set. |
| */ |
| protected final HashSet<IResource> aliases = new HashSet<>(); |
| |
| /** |
| * The set of resources that have had structure changes that might |
| * invalidate the locations map or aliased projects set. These will be |
| * updated incrementally on the next alias request. |
| */ |
| private final Set<IResource> changedLinks = new HashSet<>(); |
| |
| /** |
| * This flag is true when projects have been created or deleted and the |
| * location map has not been updated accordingly. |
| */ |
| private boolean changedProjects = false; |
| |
| /** |
| * The Doit class used for finding aliases. |
| */ |
| private final FindAliasesDoit findAliases = new FindAliasesDoit(); |
| |
| /** |
| * This maps IFileStore ->IResource, associating a file system location |
| * with the projects and/or linked resources that are rooted at that location. |
| */ |
| protected final LocationMap locationsMap = new LocationMap(); |
| /** |
| * The total number of resources in the workspace that are not in the default |
| * location. This includes all linked resources, including linked resources |
| * that don't currently have valid locations due to an undefined path variable. |
| * This also includes projects that are not in their default location. |
| * This value is used as a quick optimization, because a workspace with |
| * all resources in their default locations cannot have any aliases. |
| */ |
| private int nonDefaultResourceCount = 0; |
| |
| /** |
| * The suffix object is also used only during the computeAliases method. |
| * In this case it is a field because it is referenced from an inner class |
| * and we want to avoid creating a pointer array. It is public to eliminate |
| * the need for synthetic accessor methods. |
| */ |
| public IPath suffix; |
| |
| /** the workspace */ |
| protected final Workspace workspace; |
| |
| public AliasManager(Workspace workspace) { |
| this.workspace = workspace; |
| } |
| |
| private void addToLocationsMap(IProject project) { |
| IFileStore location = ((Resource) project).getStore(); |
| if (location != null) |
| locationsMap.add(location, project); |
| ProjectDescription description = ((Project) project).internalGetDescription(); |
| if (description == null) |
| return; |
| if (description.getLocationURI() != null) |
| nonDefaultResourceCount++; |
| HashMap<IPath, LinkDescription> links = description.getLinks(); |
| if (links == null) |
| return; |
| for (LinkDescription linkDesc : links.values()) { |
| IResource link = project.findMember(linkDesc.getProjectRelativePath()); |
| if (link != null) { |
| try { |
| URI locationURI = linkDesc.getLocationURI(); |
| locationURI = FileUtil.canonicalURI(locationURI); |
| locationURI = link.getPathVariableManager().resolveURI(locationURI); |
| addToLocationsMap(link, EFS.getStore(locationURI)); |
| } catch (CoreException e) { |
| //ignore links with invalid locations |
| } |
| } |
| } |
| } |
| |
| private void addToLocationsMap(IResource link, IFileStore location) { |
| if (location != null && !link.isVirtual()) |
| if (locationsMap.add(location, link)) |
| nonDefaultResourceCount++; |
| } |
| |
| /** |
| * Builds the table of aliased projects from scratch. |
| */ |
| private void buildAliasedProjectsSet() { |
| aliasedProjects.clear(); |
| //if there are no resources in non-default locations then there can't be any aliased projects |
| if (nonDefaultResourceCount <= 0) |
| return; |
| //for every resource that overlaps another, marked its project as aliased |
| addToCollection.setCollection(aliasedProjects); |
| locationsMap.overLappingResourcesDo(addToCollection); |
| } |
| |
| /** |
| * Builds the table of resource locations from scratch. Also computes an |
| * initial value for the linked resource counter. |
| */ |
| private void buildLocationsMap() { |
| locationsMap.clear(); |
| nonDefaultResourceCount = 0; |
| //build table of IPath (file system location) -> IResource (project or linked resource) |
| IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); |
| for (IProject project : projects) |
| if (project.isAccessible()) |
| addToLocationsMap(project); |
| } |
| |
| /** |
| * A project alias needs updating. If the project location has been deleted, |
| * then the project should be deleted from the workspace. This differs |
| * from the refresh local strategy, but operations performed from within |
| * the workspace must never leave a resource out of sync. |
| * @param project The project to check for deletion |
| * @param location The project location |
| * @return <code>true</code> if the project has been deleted, and <code>false</code> otherwise |
| * @exception CoreException |
| */ |
| private boolean checkDeletion(Project project, IFileStore location) throws CoreException { |
| if (project.exists() && !location.fetchInfo().exists()) { |
| //perform internal deletion of project from workspace tree because |
| // it is already deleted from disk and we can't acquire a different |
| //scheduling rule in this context (none is needed because we are |
| //within scope of the workspace lock) |
| Assert.isTrue(workspace.getWorkManager().getLock().getDepth() > 0); |
| project.deleteResource(false, null); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Returns all aliases of the given resource, or null if there are none. |
| */ |
| public IResource[] computeAliases(final IResource resource, IFileStore location) { |
| //nothing to do if we are or were in an alias-free workspace or project |
| if (hasNoAliases(resource)) |
| return null; |
| |
| aliases.clear(); |
| internalComputeAliases(resource, location); |
| int size = aliases.size(); |
| if (size == 0) |
| return null; |
| return aliases.toArray(new IResource[size]); |
| } |
| |
| /** |
| * Returns all resources pointing to the given location, or an empty array if there are none. |
| */ |
| public IResource[] findResources(IFileStore location) { |
| final ArrayList<IResource> resources = new ArrayList<>(); |
| locationsMap.matchingResourcesDo(location, resource -> resources.add(resource)); |
| return resources.toArray(new IResource[0]); |
| } |
| |
| /** |
| * Returns all aliases of this resource, and any aliases of subtrees of this |
| * resource. Returns null if no aliases are found. |
| */ |
| private void computeDeepAliases(IResource resource, IFileStore location) { |
| //if the location is invalid then there won't be any aliases to update |
| if (location == null) |
| return; |
| //get the normal aliases (resources rooted in parent locations) |
| internalComputeAliases(resource, location); |
| //get all resources rooted below this resource's location |
| addToCollection.setCollection(aliases); |
| locationsMap.matchingPrefixDo(location, addToCollection); |
| //if this is a project, get all resources rooted below links in this project |
| if (resource.getType() == IResource.PROJECT) { |
| try { |
| IResource[] members = ((IProject) resource).members(); |
| final FileSystemResourceManager localManager = workspace.getFileSystemManager(); |
| for (IResource member : members) { |
| if (member.isLinked()) { |
| IFileStore linkLocation = localManager.getStore(member); |
| if (linkLocation != null) |
| locationsMap.matchingPrefixDo(linkLocation, addToCollection); |
| } |
| } |
| } catch (CoreException e) { |
| //skip inaccessible projects |
| } |
| } |
| } |
| |
| /** |
| * Returns the comparator to use when sorting the locations map. Comparison |
| * is based on segments, so that paths with the most segments in common will |
| * always be adjacent. This is equivalent to the natural order on the path |
| * strings, with the extra condition that the path separator is ordered |
| * before all other characters. (Ex: "/foo" < "/foo/zzz" < "/fooaaa"). |
| */ |
| Comparator<IFileStore> getComparator() { |
| return new Comparator<IFileStore>() { |
| @Override |
| public int compare(IFileStore store1, IFileStore store2) { |
| //scheme takes precedence over all else |
| int compare = compareStringOrNull(store1.getFileSystem().getScheme(), store2.getFileSystem().getScheme()); |
| if (compare != 0) |
| return compare; |
| // compare based on URI path segment values |
| final URI uri1; |
| final URI uri2; |
| try { |
| uri1 = store1.toURI(); |
| uri2 = store2.toURI(); |
| } catch (Exception e) { |
| //protect against misbehaving 3rd party code in file system implementations |
| Policy.log(e); |
| return 1; |
| } |
| |
| // compare hosts |
| compare = compareStringOrNull(uri1.getHost(), uri2.getHost()); |
| if (compare != 0) |
| return compare; |
| // compare user infos |
| compare = compareStringOrNull(uri1.getUserInfo(), uri2.getUserInfo()); |
| if (compare != 0) |
| return compare; |
| // compare ports |
| int port1 = uri1.getPort(); |
| int port2 = uri2.getPort(); |
| if (port1 != port2) |
| return port1 - port2; |
| |
| IPath path1 = new Path(uri1.getPath()); |
| IPath path2 = new Path(uri2.getPath()); |
| // compare devices |
| compare = compareStringOrNull(path1.getDevice(), path2.getDevice()); |
| if (compare != 0) |
| return compare; |
| // compare segments |
| int segmentCount1 = path1.segmentCount(); |
| int segmentCount2 = path2.segmentCount(); |
| for (int i = 0; (i < segmentCount1) && (i < segmentCount2); i++) { |
| compare = path1.segment(i).compareTo(path2.segment(i)); |
| if (compare != 0) |
| return compare; |
| } |
| //all segments are equal, so compare based on number of segments |
| compare = segmentCount1 - segmentCount2; |
| if (compare != 0) |
| return compare; |
| //same number of segments, so compare query |
| return compareStringOrNull(uri1.getQuery(), uri2.getQuery()); |
| } |
| |
| /** |
| * Compares two strings that are possibly null. |
| */ |
| private int compareStringOrNull(String string1, String string2) { |
| if (string1 == null) { |
| if (string2 == null) |
| return 0; |
| return 1; |
| } |
| if (string2 == null) |
| return -1; |
| return string1.compareTo(string2); |
| |
| } |
| }; |
| } |
| |
| @Override |
| public void handleEvent(LifecycleEvent event) { |
| /* |
| * We can't determine the end state for most operations because they may |
| * fail after we receive pre-notification. In these cases, we remember |
| * the invalidated resources and recompute their state lazily on the |
| * next alias request. |
| */ |
| switch (event.kind) { |
| case LifecycleEvent.PRE_LINK_CHANGE : |
| case LifecycleEvent.PRE_LINK_DELETE : |
| Resource link = (Resource) event.resource; |
| if (link.isLinked()) |
| removeFromLocationsMap(link, link.getStore()); |
| //fall through |
| case LifecycleEvent.PRE_FILTER_ADD : |
| changedLinks.add(event.resource); |
| break; |
| case LifecycleEvent.PRE_FILTER_REMOVE : |
| changedLinks.add(event.resource); |
| break; |
| case LifecycleEvent.PRE_LINK_CREATE : |
| changedLinks.add(event.resource); |
| break; |
| case LifecycleEvent.PRE_LINK_COPY : |
| changedLinks.add(event.newResource); |
| break; |
| case LifecycleEvent.PRE_LINK_MOVE : |
| link = (Resource) event.resource; |
| if (link.isLinked()) |
| removeFromLocationsMap(link, link.getStore()); |
| changedLinks.add(event.newResource); |
| break; |
| } |
| } |
| |
| /** |
| * Returns true if this resource is guaranteed to have no aliases, and false |
| * otherwise. |
| */ |
| private boolean hasNoAliases(final IResource resource) { |
| //check if we're in an aliased project or workspace before updating structure changes. In the |
| //deletion case, we need to know if the resource was in an aliased project *before* deletion. |
| IProject project = resource.getProject(); |
| boolean noAliases = !aliasedProjects.contains(project); |
| |
| //now update any structure changes and check again if an update is needed |
| if (hasStructureChanges()) { |
| updateStructureChanges(); |
| noAliases &= nonDefaultResourceCount <= 0 || !aliasedProjects.contains(project); |
| } |
| return noAliases; |
| } |
| |
| /** |
| * Returns whether there are any structure changes that we have not yet processed. |
| */ |
| private boolean hasStructureChanges() { |
| return changedProjects || !changedLinks.isEmpty(); |
| } |
| |
| /** |
| * Computes the aliases of the given resource at the given location, and |
| * adds them to the "aliases" collection. |
| */ |
| private void internalComputeAliases(IResource resource, IFileStore location) { |
| IFileStore searchLocation = location; |
| if (searchLocation == null) |
| searchLocation = ((Resource) resource).getStore(); |
| //if the location is invalid then there won't be any aliases to update |
| if (searchLocation == null) |
| return; |
| |
| suffix = Path.EMPTY; |
| findAliases.setSearchAlias(resource); |
| /* |
| * Walk up the location segments for this resource, looking for a |
| * resource with a matching location. All matches are then added to the |
| * "aliases" set. |
| */ |
| do { |
| locationsMap.matchingResourcesDo(searchLocation, findAliases); |
| suffix = new Path(searchLocation.getName()).append(suffix); |
| searchLocation = searchLocation.getParent(); |
| } while (searchLocation != null); |
| } |
| |
| private void removeFromLocationsMap(IResource link, IFileStore location) { |
| if (location != null) |
| if (locationsMap.remove(location, link)) |
| nonDefaultResourceCount--; |
| } |
| |
| @Override |
| public void resourceChanged(IResourceChangeEvent event) { |
| final IResourceDelta delta = event.getDelta(); |
| if (delta == null) |
| return; |
| //invalidate location map if there are added or removed projects. |
| if (delta.getAffectedChildren(IResourceDelta.ADDED | IResourceDelta.REMOVED, IContainer.INCLUDE_HIDDEN).length > 0) |
| changedProjects = true; |
| |
| // invalidate location map if any project has the description changed |
| // or was closed/opened |
| IResourceDelta[] changed = delta.getAffectedChildren(IResourceDelta.CHANGED, IContainer.INCLUDE_HIDDEN); |
| for (IResourceDelta element : changed) { |
| if ((element.getFlags() & IResourceDelta.DESCRIPTION) == IResourceDelta.DESCRIPTION || (element.getFlags() & IResourceDelta.OPEN) == IResourceDelta.OPEN) { |
| changedProjects = true; |
| break; |
| } |
| } |
| } |
| |
| @Override |
| public void shutdown(IProgressMonitor monitor) { |
| workspace.removeResourceChangeListener(this); |
| locationsMap.clear(); |
| } |
| |
| @Override |
| public void startup(IProgressMonitor monitor) { |
| workspace.addLifecycleListener(this); |
| workspace.addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE); |
| buildLocationsMap(); |
| buildAliasedProjectsSet(); |
| } |
| |
| /** |
| * The file underlying the given resource has changed on disk. Compute all |
| * aliases for this resource and update them. This method will not attempt |
| * to incur any units of work on the given progress monitor, but it may |
| * update the subtask to reflect what aliases are being updated. |
| * @param resource the resource to compute aliases for |
| * @param location the file system location of the resource (passed as a |
| * parameter because in the project deletion case the resource is no longer |
| * accessible at time of update). |
| * @param depth whether to search for aliases on all children of the given |
| * resource. Only depth ZERO and INFINITE are used. |
| */ |
| @SuppressWarnings({"unchecked"}) |
| public void updateAliases(IResource resource, IFileStore location, int depth, IProgressMonitor monitor) throws CoreException { |
| if (hasNoAliases(resource)) |
| return; |
| aliases.clear(); |
| if (depth == IResource.DEPTH_ZERO) |
| internalComputeAliases(resource, location); |
| else |
| computeDeepAliases(resource, location); |
| if (aliases.isEmpty()) |
| return; |
| FileSystemResourceManager localManager = workspace.getFileSystemManager(); |
| HashSet<IResource> aliasesCopy = (HashSet<IResource>) aliases.clone(); |
| for (IResource alias : aliasesCopy) { |
| monitor.subTask(NLS.bind(Messages.links_updatingDuplicate, alias.getFullPath())); |
| if (alias.getType() == IResource.PROJECT) { |
| if (checkDeletion((Project) alias, location)) |
| continue; |
| //project did not require deletion, so fall through below and refresh it |
| } |
| if (!((Resource) alias).isFiltered()) |
| localManager.refresh(alias, IResource.DEPTH_INFINITE, false, null); |
| } |
| } |
| |
| /** |
| * Process any structural changes that have occurred since the last alias |
| * request. |
| */ |
| private void updateStructureChanges() { |
| boolean hadChanges = false; |
| if (changedProjects) { |
| //if a project is added or removed, just recompute the whole world |
| changedProjects = false; |
| hadChanges = true; |
| buildLocationsMap(); |
| } else { |
| //incrementally update location map for changed links |
| for (IResource resource : changedLinks) { |
| hadChanges = true; |
| if (!resource.isAccessible()) |
| continue; |
| if (resource.isLinked()) |
| addToLocationsMap(resource, ((Resource) resource).getStore()); |
| } |
| } |
| changedLinks.clear(); |
| if (hadChanges) |
| buildAliasedProjectsSet(); |
| changedProjects = false; |
| } |
| } |