blob: 8bf2a922ff8179d7e443b38860c98fc14f5ae0c4 [file] [log] [blame]
/**********************************************************************
* Copyright (c) 2002 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM - Initial API and implementation
**********************************************************************/
package org.eclipse.core.internal.resources;
import java.util.*;
import org.eclipse.core.internal.events.ILifecycleListener;
import org.eclipse.core.internal.events.LifecycleEvent;
import org.eclipse.core.internal.utils.Policy;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.*;
/**
* 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 {
/**
* Maintains a mapping of IPath->IResource, such that multiple resources
* mapped from the same path are tolerated.
*/
class LocationMap {
/**
* Map of IPath->IResource OR IPath->ArrayList of (IResource)
*/
private final SortedMap 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(IPath 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 newValue = new ArrayList(2);
newValue.add(oldValue);
newValue.add(resource);
map.put(location, newValue);
return true;
}
ArrayList list = (ArrayList)oldValue;
if (list.contains(resource))
return false;//duplicate
list.add(resource);
return true;
}
/**
* Method clear.
*/
public void clear() {
map.clear();
}
/**
* Calls the given doit with every resource in the map whose location
* overlaps another resource in the map.
*/
public void overLappingResourcesDo(Doit doit) {
Iterator entries = map.entrySet().iterator();
IPath previousPath = null;
IResource previousResource = null;
while (entries.hasNext()) {
Map.Entry current = (Map.Entry)entries.next();
//value is either single resource or List of resources
IPath currentPath = (IPath)current.getKey();
IResource currentResource = null;
Object value = current.getValue();
if (value instanceof List) {
//if there are several then they're all overlapping
Iterator duplicates = ((List)value).iterator();
while (duplicates.hasNext())
doit.doit((IResource)duplicates.next());
} else {
//value is a single resource
currentResource = (IResource)value;
}
if (previousPath != null) {
//check for overlap with previous
//Note: previous is always shorter due to map sorting rules
if (previousPath.isPrefixOf(currentPath)) {
//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);
if (currentResource != null)
doit.doit(currentResource);
}
}
previousPath = currentPath;
previousResource = currentResource;
}
}
/**
* Removes the given location from the map. Returns true if anything
* was actually removed, and false otherwise.
*/
public boolean remove(IPath 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;
}
ArrayList list = (ArrayList)oldValue;
boolean wasRemoved = list.remove(resource);
if (list.size() == 0)
map.remove(location);
return wasRemoved;
}
}
interface Doit {
public void doit(IResource resource);
}
/**
* This maps IPath->IResource, where the path is an absolute file system
* location, and the resource contains the projects and/or linked resources
* that are rooted at that location.
*/
private final LocationMap locationsMap = new LocationMap();
/**
* The set of IProjects that have aliases.
*/
private final Set aliasedProjects = new HashSet();
/**
* The total number of linked resources that exist in the workspace. This
* count includes linked resources that don't currently have valid locations
* (due to an undefined path variable).
*/
private int linkedResourceCount = 0;
/** the workspace */
private final Workspace workspace;
public AliasManager(Workspace workspace) {
this.workspace = workspace;
}
private void addToLocationsMap(IProject project) {
IPath location = project.getLocation();
if (location != null)
locationsMap.add(location, project);
try {
IResource[] members = project.members();
if (members != null)
//look for linked resources
for (int i = 0; i < members.length; i++)
if (members[i].isLinked())
addToLocationsMap(members[i]);
} catch (CoreException e) {
//skip inaccessible projects
}
}
private void addToLocationsMap(IResource linkedResource) {
IPath location = linkedResource.getLocation();
if (location != null)
if (locationsMap.add(location, linkedResource))
linkedResourceCount++;
}
/**
* Builds the table of aliased projects from scratch.
*/
private void buildAliasedProjectsSet() {
aliasedProjects.clear();
//if there are no linked resources then there can't be any aliased projects
if (linkedResourceCount <= 0) {
//paranoid check -- count should never be below zero
// Assert.isTrue(linkedResourceCount == 0, "Linked resource count below zero");//$NON-NLS-1$
return;
}
//for every resource that overlaps another, marked its project as aliased
locationsMap.overLappingResourcesDo(new Doit() {
public void doit(IResource resource) {
aliasedProjects.add(resource.getProject());
}
});
}
/**
* Builds the table of resource locations from scratch. Also computes an
* initial value for the linked resource counter.
*/
private void buildLocationsMap() {
locationsMap.clear();
linkedResourceCount = 0;
//build table of IPath (file system location) -> IResource (project or linked resource)
IProject[] projects = workspace.getRoot().getProjects();
for (int i = 0; i < projects.length; i++) {
addToLocationsMap(projects[i]);
}
}
public void removeFromLocationsMap(IProject project) {
//remove this project and all linked children from the location table
IPath location = project.getLocation();
if (location != null)
locationsMap.remove(location, project);
IResource[] children = null;
try {
children = project.members();
} catch (CoreException e) {
//ignore inaccessible projects
}
if (children != null) {
for (int i = 0; i < children.length; i++) {
if (children[i].isLinked()) {
removeFromLocationsMap(children[i]);
}
}
}
}
public void removeFromLocationsMap(IResource linkedResource) {
//this linked resource is being deleted
IPath location = linkedResource.getLocation();
if (location != null)
if (locationsMap.remove(location, linkedResource));
linkedResourceCount--;
}
/**
* 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").
*/
private Comparator getComparator() {
return new Comparator() {
public int compare(Object o1, Object o2) {
IPath path1 = (IPath) o1;
IPath path2 = (IPath) o2;
int segmentCount1 = path1.segmentCount();
int segmentCount2 = path2.segmentCount();
for (int i = 0; (i < segmentCount1) && (i < segmentCount2); i++) {
String segment1 = path1.segment(i);
String segment2 = path2.segment(i);
int compare = segment1.compareTo(segment2);
if (compare != 0)
return compare;
}
//all segments are equal, so they are the same if they have the same number of segments
return segmentCount1-segmentCount2;
}
};
}
public void handleEvent(LifecycleEvent event) throws CoreException {
switch (event.kind) {
case LifecycleEvent.PRE_PROJECT_CLOSE:
case LifecycleEvent.PRE_PROJECT_DELETE:
removeFromLocationsMap((IProject)event.resource);
break;
case LifecycleEvent.PRE_PROJECT_MOVE:
case LifecycleEvent.PRE_PROJECT_OPEN:
addToLocationsMap((IProject)event.resource);
buildAliasedProjectsSet();
break;
case LifecycleEvent.PRE_LINK_DELETE:
case LifecycleEvent.PRE_LINK_MOVE:
case LifecycleEvent.POST_LINK_CREATE:
}
//rebuild the set of aliased projects from scratch
buildAliasedProjectsSet();
}
/**
* Method moving.
* @param source
* @param destination
* @param updateFlags
*/
public void moving(IResource source, IResource destination, int updateFlags) {
// todo
}
/**
* @see IManager#shutdown
*/
public void shutdown(IProgressMonitor monitor) throws CoreException {
}
/**
* @see IManager#startup
*/
public void startup(IProgressMonitor monitor) throws CoreException {
workspace.addLifecycleListener(this);
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.
*/
public void updateAliases(IResource resource, IProgressMonitor monitor) throws CoreException {
//nothing to do if we're in an alias-free workspace or project
if (linkedResourceCount <= 0 || !aliasedProjects.contains(resource.getProject()))
return;
IResource[] aliases = computeAliases(resource);
if (aliases == null)
return;
for (int i = 0; i < aliases.length; i++) {
monitor.subTask(Policy.bind("links.updatingDuplicate", resource.getFullPath().toString()));//$NON-NLS-1$
aliases[i].refreshLocal(IResource.DEPTH_INFINITE, null);
}
}
/**
* Returns all aliases of the given resource, or null if there are none.
* This list will NOT include the resource itself.
*/
private IResource[] computeAliases(IResource resource) {
// todo
return null;
}
/**
* Notification of creation of a linked reousr.cds
*/
public void creating(IResource linkedResource) {
addToLocationsMap(linkedResource);
buildAliasedProjectsSet();
}
}