blob: d093a1f64716ac3b5333ab66e4ee5b7c326e7717 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 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.io.IOException;
import java.util.*;
import org.eclipse.core.internal.events.*;
import org.eclipse.core.internal.localstore.CoreFileSystemLibrary;
import org.eclipse.core.internal.localstore.FileSystemResourceManager;
import org.eclipse.core.internal.properties.PropertyManager;
import org.eclipse.core.internal.utils.*;
import org.eclipse.core.internal.watson.*;
import org.eclipse.core.resources.*;
import org.eclipse.core.resources.team.IMoveDeleteHook;
import org.eclipse.core.resources.team.TeamHook;
import org.eclipse.core.runtime.*;
public class Workspace extends PlatformObject implements IWorkspace, ICoreConstants {
protected WorkspaceDescription description;
protected LocalMetaArea localMetaArea;
protected boolean openFlag = false;
protected ElementTree tree;
protected ElementTree operationTree; // tree at the start of the current operation
protected SaveManager saveManager;
protected BuildManager buildManager;
protected NatureManager natureManager;
protected NotificationManager notificationManager;
protected FileSystemResourceManager fileSystemManager;
protected PathVariableManager pathVariableManager;
protected PropertyManager propertyManager;
protected MarkerManager markerManager;
protected WorkManager workManager;
protected AliasManager aliasManager;
protected long nextNodeId = 0;
protected long nextModificationStamp = 0;
protected long nextMarkerId = 0;
protected Synchronizer synchronizer;
protected IProject[] buildOrder = null;
protected IWorkspaceRoot defaultRoot = new WorkspaceRoot(Path.ROOT, this);
protected final ArrayList lifecycleListeners = new ArrayList(10);
protected static final String REFRESH_ON_STARTUP = "-refresh"; //$NON-NLS-1$
/**
* File modification validation. If it is true and validator is null, we try/initialize
* validator first time through. If false, there is no validator.
*/
protected boolean shouldValidate = true;
/**
* The currently installed file modification validator.
*/
protected IFileModificationValidator validator = null;
/**
* The currently installed Move/Delete hook.
*/
protected IMoveDeleteHook moveDeleteHook = null;
/**
* The currently installed team hook.
*/
protected TeamHook teamHook = null;
// whether the resources plugin is in debug mode.
public static boolean DEBUG = false;
/**
This field is used to control the access to the workspace tree
inside operations. It is useful when calling alien code. Since
we usually are in the same thread as the alien code we are calling,
our concurrency model would allow the alien code to run operations
that could change the tree. If this field is set to true, a
beginOperation(true) fails, so the alien code would fail and be logged
in our SafeRunnable wrappers, not affecting the normal workspace operation.
*/
protected boolean treeLocked;
/** indicates if the workspace crashed in a previous session */
protected boolean crashed = false;
public Workspace() {
super();
localMetaArea = new LocalMetaArea();
tree = new ElementTree();
/* tree should only be modified during operations */
tree.immutable();
treeLocked = true;
tree.setTreeData(newElement(IResource.ROOT));
}
/**
* Adds a listener for internal workspace lifecycle events. There is no way to
* remove lifecycle listeners.
*/
public void addLifecycleListener(ILifecycleListener listener) {
lifecycleListeners.add(listener);
}
/**
* @see IWorkspace
*/
public void addResourceChangeListener(IResourceChangeListener listener) {
notificationManager.addListener(listener, IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE | IResourceChangeEvent.POST_CHANGE);
}
/**
* @see IWorkspace
*/
public void addResourceChangeListener(IResourceChangeListener listener, int eventMask) {
notificationManager.addListener(listener, eventMask);
}
/**
* @see IWorkspace
*/
public ISavedState addSaveParticipant(Plugin plugin, ISaveParticipant participant) throws CoreException {
Assert.isNotNull(plugin, "Plugin must not be null"); //$NON-NLS-1$
Assert.isNotNull(participant, "Participant must not be null"); //$NON-NLS-1$
return saveManager.addParticipant(plugin, participant);
}
public void beginOperation(boolean createNewTree) throws CoreException {
WorkManager workManager = getWorkManager();
workManager.incrementNestedOperations();
if (!workManager.isBalanced())
Assert.isTrue(false, "Operation was not prepared."); //$NON-NLS-1$
if (treeLocked && createNewTree) {
String message = Policy.bind("resources.cannotModify"); //$NON-NLS-1$
throw new ResourceException(IResourceStatus.ERROR, null, message, null);
}
if (workManager.getPreparedOperationDepth() > 1) {
if (createNewTree && tree.isImmutable())
newWorkingTree();
return;
}
if (createNewTree) {
// stash the current tree as the basis for this operation.
operationTree = tree;
newWorkingTree();
}
}
private void broadcastChanges(ElementTree currentTree, int type, boolean lockTree, boolean updateState, IProgressMonitor monitor) {
if (operationTree == null)
return;
monitor.subTask(Policy.bind("resources.updating")); //$NON-NLS-1$
notificationManager.broadcastChanges(currentTree, type, lockTree, updateState);
}
/**
* Broadcasts an internal workspace lifecycle event to interested
* internal listeners.
*/
protected void broadcastEvent(LifecycleEvent event) throws CoreException {
}
public void build(int trigger, IProgressMonitor monitor) throws CoreException {
monitor = Policy.monitorFor(monitor);
try {
monitor.beginTask(null, Policy.opWork);
try {
prepareOperation();
beginOperation(true);
getBuildManager().build(trigger, Policy.subMonitorFor(monitor, Policy.opWork));
} finally {
getWorkManager().avoidAutoBuild();
endOperation(false, Policy.subMonitorFor(monitor, Policy.buildWork));
}
} finally {
monitor.done();
}
}
/**
* Notify the relevant infrastructure pieces that the given project is being
* changed (e.g., setDescription, move, copy...) and various parts of
* the workspace need to be
* notified so they can update cached structures etc.
*/
public void changing(IProject project) throws CoreException {
buildManager.changing(project);
natureManager.changing(project);
notificationManager.changing(project);
propertyManager.changing(project);
markerManager.changing(project);
aliasManager.changing(project);
}
/**
* @see IWorkspace#checkpoint
*/
public void checkpoint(boolean build) {
boolean immutable = true;
try {
/* if it was not called by the current operation, just ignore */
if (!getWorkManager().isCurrentOperation())
return;
immutable = tree.isImmutable();
broadcastChanges(tree, IResourceChangeEvent.PRE_AUTO_BUILD, false, false, Policy.monitorFor(null));
if (build && isAutoBuilding())
getBuildManager().build(IncrementalProjectBuilder.AUTO_BUILD, Policy.monitorFor(null));
broadcastChanges(tree, IResourceChangeEvent.POST_AUTO_BUILD, false, false, Policy.monitorFor(null));
broadcastChanges(tree, IResourceChangeEvent.POST_CHANGE, true, true, Policy.monitorFor(null));
getMarkerManager().resetMarkerDeltas();
} catch (CoreException e) {
// ignore any CoreException. There shouldn't be any as the buildmanager and notification manager
// should be catching and logging...
} finally {
if (!immutable)
newWorkingTree();
}
}
/**
* Deletes all the files and directories from the given root down (inclusive).
* Returns false if we could not delete some file or an exception occurred
* at any point in the deletion.
* Even if an exception occurs, a best effort is made to continue deleting.
*/
public static boolean clear(java.io.File root) {
boolean result = true;
if (root.isDirectory()) {
String[] list = root.list();
// for some unknown reason, list() can return null.
// Just skip the children If it does.
if (list != null)
for (int i = 0; i < list.length; i++)
result &= clear(new java.io.File(root, list[i]));
}
try {
if (root.exists())
result &= root.delete();
} catch (Exception e) {
result = false;
}
return result;
}
/**
* Closes this workspace; ignored if this workspace is not open.
* The state of this workspace is not saved before the workspace
* is shut down.
* <p>
* If the workspace was saved immediately prior to closing,
* it will have the same set of projects
* (open or closed) when reopened for a subsequent session.
* Otherwise, closing a workspace may lose some or all of the
* changes made since the last save or snapshot.
* </p>
* <p>
* Note that session properties are discarded when a workspace is closed.
* </p>
* <p>
* This method is long-running; progress and cancellation are provided
* by the given progress monitor.
* </p>
*
* @param monitor a progress monitor, or <code>null</code> if progress
* reporting and cancellation are not desired
* @exception CoreException if the workspace could not be shutdown.
*/
public void close(IProgressMonitor monitor) throws CoreException {
monitor = Policy.monitorFor(monitor);
try {
String msg = Policy.bind("resources.closing.0"); //$NON-NLS-1$
int rootCount = tree.getChildCount(Path.ROOT);
monitor.beginTask(msg, rootCount + 2);
monitor.subTask(msg);
//this operation will never end because the world is going away
try {
prepareOperation();
if (isOpen()) {
beginOperation(true);
IProject[] projects = getRoot().getProjects();
for (int i = 0; i < projects.length; i++) {
//notify managers of closing so they can cleanup
closing(projects[i]);
monitor.worked(1);
}
//empty the workspace tree so we leave in a clean state
deleteResource(getRoot());
openFlag = false;
}
// endOperation not needed here
} finally {
// Shutdown needs to be executed anyway. Doesn't matter if the workspace was not open.
shutdown(Policy.subMonitorFor(monitor, 2, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL));
}
} finally {
monitor.done();
}
}
/**
* Notify the relevant infrastructure pieces that the given project is being
* closed.
*/
protected void closing(IProject project) throws CoreException {
buildManager.closing(project);
natureManager.closing(project);
notificationManager.closing(project);
propertyManager.closing(project);
markerManager.closing(project);
aliasManager.closing(project);
}
/**
* Implementation of API method declared on IWorkspace.
*
* @deprecated Replaced by <code>IWorkspace.computeProjectOrder</code>, which
* produces a more usable result when there are cycles in project reference
* graph.
*/
public IProject[][] computePrerequisiteOrder(IProject[] targets) {
return computePrerequisiteOrder1(targets);
}
/*
* Compatible reimplementation of
* <code>IWorkspace.computePrerequisiteOrder</code> using
* <code>IWorkspace.computeProjectOrder</code>.
*
* @since 2.1
*/
private IProject[][] computePrerequisiteOrder1(IProject[] projects) {
IWorkspace.ProjectOrder r = computeProjectOrder(projects);
if (!r.hasCycles) {
return new IProject[][] {r.projects, new IProject[0]};
}
// when there are cycles, we need to remove all knotted projects from
// r.projects to form result[0] and merge all knots to form result[1]
// Set<IProject> bad
Set bad = new HashSet();
// Set<IProject> bad
Set keepers = new HashSet(Arrays.asList(r.projects));
for (int i = 0; i < r.knots.length; i++) {
IProject[] knot = r.knots[i];
for (int j = 0; j < knot.length; j++) {
IProject project = knot[j];
// keep only selected projects in knot
if (keepers.contains(project)) {
bad.add(project);
}
}
}
IProject[] result2 = new IProject[bad.size()];
bad.toArray(result2);
// List<IProject> p
List p = new LinkedList();
p.addAll(Arrays.asList(r.projects));
for (Iterator it = p.listIterator(); it.hasNext(); ) {
IProject project = (IProject) it.next();
if (bad.contains(project)) {
// remove knotted projects from the main answer
it.remove();
}
}
IProject[] result1 = new IProject[p.size()];
p.toArray(result1);
return new IProject[][] {result1, result2};
}
/**
* Implementation of API method declared on IWorkspace.
*
* @since 2.1
*/
public ProjectOrder computeProjectOrder(IProject[] projects) {
// compute the full project order for all accessible projects
ProjectOrder fullProjectOrder = computeFullProjectOrder();
// "fullProjectOrder.projects" contains no inaccessible projects
// but might contain accessible projects omitted from "projects"
// optimize common case where "projects" includes everything
int accessibleCount = 0;
for (int i = 0; i < projects.length; i++) {
if (projects[i].isAccessible()) {
accessibleCount++;
}
}
// no filtering required if the subset accounts for the full list
if (accessibleCount == fullProjectOrder.projects.length) {
return fullProjectOrder;
}
// otherwise we need to eliminate mention of other projects...
// ... from "fullProjectOrder.projects"...
// Set<IProject> keepers
Set keepers = new HashSet(Arrays.asList(projects));
// List<IProject> p
List reducedProjects = new ArrayList(fullProjectOrder.projects.length);
for (int i = 0; i < fullProjectOrder.projects.length; i++) {
IProject project = fullProjectOrder.projects[i];
if (keepers.contains(project)) {
// remove projects not in the initial subset
reducedProjects.add(project);
}
}
IProject[] p1 = new IProject[reducedProjects.size()];
reducedProjects.toArray(p1);
// ... and from "fullProjectOrder.knots"
// List<IProject[]> k
List reducedKnots = new ArrayList(fullProjectOrder.knots.length);
for (int i = 0; i < fullProjectOrder.knots.length; i++) {
IProject[] knot = fullProjectOrder.knots[i];
List x = new ArrayList(knot.length);
for (int j = 0; j < knot.length; j++) {
IProject project = knot[j];
if (keepers.contains(project)) {
x.add(project);
}
}
// keep knots containing 2 or more projects in the specified subset
if (x.size() > 1) {
reducedKnots.add(x.toArray(new IProject[x.size()]));
}
}
IProject[][] k1 = new IProject[reducedKnots.size()][];
// okay to use toArray here because reducedKnots elements are IProject[]
reducedKnots.toArray(k1);
return new ProjectOrder(p1, (k1.length > 0), k1);
}
/**
* Computes the global total ordering of all open projects in the
* workspace based on project references. If an existing and open project P
* references another existing and open project Q also included in the list,
* then Q should come before P in the resulting ordering. Closed and non-
* existent projects are ignored, and will not appear in the result. References
* to non-existent or closed projects are also ignored, as are any self-
* references.
* <p>
* When there are choices, the choice is made in a reasonably stable way. For
* example, given an arbitrary choice between two projects, the one with the
* lower collating project name is usually selected.
* </p>
* <p>
* When the project reference graph contains cyclic references, it is
* impossible to honor all of the relationships. In this case, the result
* ignores as few relationships as possible. For example, if P2 references P1,
* P4 references P3, and P2 and P3 reference each other, then exactly one of the
* relationships between P2 and P3 will have to be ignored. The outcome will be
* either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result also contains
* complete details of any cycles present.
* </p>
*
* @return result describing the global project order
* @since 2.1
*/
private ProjectOrder computeFullProjectOrder() {
// determine the full set of accessible projects in the workspace
// order the set in descending alphabetical order of project name
SortedSet allAccessibleProjects = new TreeSet(new Comparator() {
public int compare(Object x, Object y) {
IProject px = (IProject) x;
IProject py = (IProject) y;
return py.getName().compareTo(px.getName());
}});
IProject[] allProjects = getRoot().getProjects();
// List<IProject[]> edges
List edges = new ArrayList(allProjects.length);
for (int i = 0; i < allProjects.length; i++) {
IProject project = allProjects[i];
// ignore projects that are not accessible
if (project.isAccessible()) {
allAccessibleProjects.add(project);
IProject[] refs= null;
try {
refs = project.getReferencedProjects();
} catch (CoreException e) {
// can't happen - project is accessible
}
for (int j = 0; j < refs.length; j++) {
IProject ref = refs[j];
// ignore self references and references to projects that are
// not accessible
if (ref.isAccessible() && !ref.equals(project)) {
edges.add(new IProject[] {project, ref});
}
}
}
}
ProjectOrder fullProjectOrder =
ComputeProjectOrder.computeProjectOrder(allAccessibleProjects, edges);
return fullProjectOrder;
}
/*
* @see IWorkspace#copy
*/
public IStatus copy(IResource[] resources, IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException {
monitor = Policy.monitorFor(monitor);
try {
int opWork = Math.max(resources.length, 1);
int totalWork = Policy.totalWork * opWork / Policy.opWork;
String message = Policy.bind("resources.copying.0"); //$NON-NLS-1$
monitor.beginTask(message, totalWork);
Assert.isLegal(resources != null);
if (resources.length == 0)
return ResourceStatus.OK_STATUS;
// to avoid concurrent changes to this array
resources = (IResource[]) resources.clone();
IPath parentPath = null;
message = Policy.bind("resources.copyProblem"); //$NON-NLS-1$
MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null);
try {
prepareOperation();
beginOperation(true);
for (int i = 0; i < resources.length; i++) {
Policy.checkCanceled(monitor);
IResource resource = resources[i];
if (resource == null || isDuplicate(resources, i)) {
monitor.worked(1);
continue;
}
// test siblings
if (parentPath == null)
parentPath = resource.getFullPath().removeLastSegments(1);
if (parentPath.equals(resource.getFullPath().removeLastSegments(1))) {
// test copy requirements
try {
IPath destinationPath = destination.append(resource.getName());
IStatus requirements = ((Resource) resource).checkCopyRequirements(destinationPath, resource.getType(), updateFlags);
if (requirements.isOK()) {
try {
resource.copy(destinationPath, updateFlags, Policy.subMonitorFor(monitor, 1));
} catch (CoreException e) {
status.merge(e.getStatus());
}
} else {
monitor.worked(1);
status.merge(requirements);
}
} catch (CoreException e) {
monitor.worked(1);
status.merge(e.getStatus());
}
} else {
monitor.worked(1);
message = Policy.bind("resources.notChild", resources[i].getFullPath().toString(), parentPath.toString()); //$NON-NLS-1$
status.merge(new ResourceStatus(IResourceStatus.OPERATION_FAILED, resources[i].getFullPath(), message));
}
}
} catch (OperationCanceledException e) {
getWorkManager().operationCanceled();
throw e;
} finally {
endOperation(true, Policy.subMonitorFor(monitor, totalWork - opWork));
}
if (status.matches(IStatus.ERROR))
throw new ResourceException(status);
return status.isOK() ? ResourceStatus.OK_STATUS : (IStatus) status;
} finally {
monitor.done();
}
}
/**
* @see IWorkspace#copy
*/
public IStatus copy(IResource[] resources, IPath destination, boolean force, IProgressMonitor monitor) throws CoreException {
int updateFlags = force ? IResource.FORCE : IResource.NONE;
return copy(resources, destination, updateFlags , monitor);
}
protected void copyTree(IResource source, IPath destination, int depth, boolean phantom, boolean overwrite) throws CoreException {
copyTree(source, destination, depth, phantom, overwrite, false);
}
protected void copyTree(IResource source, IPath destination, int depth, boolean phantom, boolean overwrite, boolean keepSyncInfo) throws CoreException {
// FIXME: change this method signature to use the int updateFlags rather than the booleans.
// retrieve the resource at the destination if there is one (phantoms included).
// if there isn't one, then create a new handle based on the type that we are
// trying to copy
IResource destinationResource = getRoot().findMember(destination, true);
if (destinationResource == null) {
int destinationType;
if (source.getType() == IResource.FILE)
destinationType = IResource.FILE;
else
if (destination.segmentCount() == 1)
destinationType = IResource.PROJECT;
else
destinationType = IResource.FOLDER;
destinationResource = newResource(destination, destinationType);
}
// create the resource at the destination
ResourceInfo sourceInfo = ((Resource) source).getResourceInfo(true, false);
if (destinationResource.getType() != source.getType()) {
sourceInfo = (ResourceInfo) sourceInfo.clone();
sourceInfo.setType(destinationResource.getType());
}
ResourceInfo newInfo = createResource(destinationResource, sourceInfo, phantom, overwrite, keepSyncInfo);
// get/set the node id from the source's resource info so we can later put it in the
// info for the destination resource. This will help us generate the proper deltas,
// indicating a move rather than a add/delete
long nodeid = ((Resource) source).getResourceInfo(true, false).getNodeId();
newInfo.setNodeId(nodeid);
// preserve local sync info
ResourceInfo oldInfo = ((Resource) source).getResourceInfo(true, false);
newInfo.setFlags(newInfo.getFlags() | (oldInfo.getFlags() & M_LOCAL_EXISTS));
// update link locations in project descriptions
if (source.isLinked()) {
newInfo.set(ICoreConstants.M_LINK);
//create new link location
Project project = (Project)destinationResource.getProject();
project.internalGetDescription().setLinkLocation(destinationResource.getName(),
new LinkDescription(destinationResource, source.getLocation()));
project.writeDescription(IResource.NONE);
}
// do the recursion. if we have a file then it has no members so return. otherwise
// recursively call this method on the container's members if the depth tells us to
if (depth == IResource.DEPTH_ZERO || source.getType() == IResource.FILE)
return;
if (depth == IResource.DEPTH_ONE)
depth = IResource.DEPTH_ZERO;
int flags = phantom ? IContainer.INCLUDE_PHANTOMS : IResource.NONE;
flags |= IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS;
IResource[] children = ((IContainer) source).members(flags);
for (int i = 0; i < children.length; i++) {
IResource child = children[i];
IPath childPath = destination.append(child.getName());
copyTree(child, childPath, depth, phantom, overwrite, keepSyncInfo);
}
}
/**
* Returns the number of resources in a subtree of the resource tree.
* @param path The subtree to count resources for
* @param depth The depth of the subtree to count
* @param phantom If true, phantoms are included, otherwise they are ignored.
*/
public int countResources(IPath root, int depth, final boolean phantom) {
if (!tree.includes(root))
return 0;
switch (depth) {
case IResource.DEPTH_ZERO:
return 1;
case IResource.DEPTH_ONE:
return 1 + tree.getChildCount(root);
case IResource.DEPTH_INFINITE:
final int[] count = new int[1];
IElementContentVisitor visitor = new IElementContentVisitor() {
public void visitElement(ElementTree tree, IPath elementPath, Object elementContents) {
if (phantom || !((ResourceInfo)elementContents).isSet(M_PHANTOM))
count[0]++;
}
};
new ElementTreeIterator().iterate(tree, visitor, root);
return count[0];
}
return 0;
}
public ResourceInfo createResource(IResource resource, ResourceInfo info, boolean phantom, boolean overwrite) throws CoreException {
return createResource(resource, info, phantom, overwrite, false);
}
/*
* Creates the given resource in the tree and returns the new resource info object.
* If phantom is true, the created element is marked as a phantom.
* If there is already be an element in the tree for the given resource
* in the given state (i.e., phantom), a CoreException is thrown.
* If there is already a phantom in the tree and the phantom flag is false,
* the element is overwritten with the new element. (but the synchronization
* information is preserved) If the specified resource info is null, then create
* a new one.
*
* If keepSyncInfo is set to be true, the sync info in the given ResourceInfo is NOT
* cleared before being created and thus any sync info already existing at that namespace
* (as indicated by an already existing phantom resource) will be lost.
*/
public ResourceInfo createResource(IResource resource, ResourceInfo info, boolean phantom, boolean overwrite, boolean keepSyncInfo) throws CoreException {
info = info == null ? newElement(resource.getType()) : (ResourceInfo) info.clone();
ResourceInfo original = getResourceInfo(resource.getFullPath(), true, false);
if (phantom) {
info.set(M_PHANTOM);
info.setModificationStamp(IResource.NULL_STAMP);
}
// if nothing existed at the destination then just create the resource in the tree
if (original == null) {
// we got here from a copy/move. we don't want to copy over any sync info
// from the source so clear it.
if (!keepSyncInfo)
info.setSyncInfo(null);
tree.createElement(resource.getFullPath(), info);
} else {
// if overwrite==true then slam the new info into the tree even if one existed before
if (overwrite || (!phantom && original.isSet(M_PHANTOM))) {
// copy over the sync info and flags from the old resource info
// since we are replacing a phantom with a real resource
// DO NOT set the sync info dirty flag because we want to
// preserve the old sync info so its not dirty
// XXX: must copy over the generic sync info from the old info to the new
// XXX: do we really need to clone the sync info here?
if (!keepSyncInfo)
info.setSyncInfo(original.getSyncInfo(true));
// mark the markers bit as dirty so we snapshot an empty marker set for
// the new resource
info.set(ICoreConstants.M_MARKERS_SNAP_DIRTY);
tree.setElementData(resource.getFullPath(), info);
} else {
String message = Policy.bind("resources.mustNotExist", resource.getFullPath().toString()); //$NON-NLS-1$
throw new ResourceException(IResourceStatus.RESOURCE_EXISTS, resource.getFullPath(), message, null);
}
}
return info;
}
/**
* Notify relevant infrastructure pieces that a linked resource is being
* created.
*/
public void creating(IResource linkedResource) {
aliasManager.creating(linkedResource);
}
/*
* Creates the given resource in the tree and returns the new resource info object.
* If phantom is true, the created element is marked as a phantom.
* If there is already be an element in the tree for the given resource
* in the given state (i.e., phantom), a CoreException is thrown.
* If there is already a phantom in the tree and the phantom flag is false,
* the element is overwritten with the new element. (but the synchronization
* information is preserved)
*/
public ResourceInfo createResource(IResource resource, boolean phantom) throws CoreException {
return createResource(resource, null, phantom, false);
}
public ResourceInfo createResource(IResource resource, boolean phantom, boolean overwrite) throws CoreException {
return createResource(resource, null, phantom, overwrite);
}
public static WorkspaceDescription defaultWorkspaceDescription() {
return new WorkspaceDescription("Workspace"); //$NON-NLS-1$
}
/*
* @see IWorkspace#delete
*/
public IStatus delete(IResource[] resources, int updateFlags, IProgressMonitor monitor) throws CoreException {
monitor = Policy.monitorFor(monitor);
try {
int opWork = Math.max(resources.length, 1);
int totalWork = Policy.totalWork * opWork / Policy.opWork;
String message = Policy.bind("resources.deleting.0"); //$NON-NLS-1$
monitor.beginTask(message, totalWork);
message = Policy.bind("resources.deleteProblem"); //$NON-NLS-1$
MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null);
if (resources.length == 0)
return result;
resources = (IResource[]) resources.clone(); // to avoid concurrent changes to this array
try {
prepareOperation();
beginOperation(true);
for (int i = 0; i < resources.length; i++) {
Policy.checkCanceled(monitor);
Resource resource = (Resource) resources[i];
if (resource == null) {
monitor.worked(1);
continue;
}
try {
resource.delete(updateFlags, Policy.subMonitorFor(monitor, 1));
} catch (CoreException e) {
// Don't really care about the exception unless the resource is still around.
ResourceInfo info = resource.getResourceInfo(false, false);
if (resource.exists(resource.getFlags(info), false)) {
message = Policy.bind("resources.couldnotDelete", resource.getFullPath().toString()); //$NON-NLS-1$
result.merge(new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, resource.getFullPath(), message));
result.merge(e.getStatus());
}
}
}
if (result.matches(IStatus.ERROR))
throw new ResourceException(result);
return result;
} catch (OperationCanceledException e) {
getWorkManager().operationCanceled();
throw e;
} finally {
endOperation(true, Policy.subMonitorFor(monitor, totalWork - opWork));
}
} finally {
monitor.done();
}
}
/*
* @see IWorkspace#delete
*/
public IStatus delete(IResource[] resources, boolean force, IProgressMonitor monitor) throws CoreException {
int updateFlags = force ? IResource.FORCE : IResource.NONE;
updateFlags |= IResource.KEEP_HISTORY;
return delete(resources, updateFlags, monitor);
}
/**
* @see IWorkspace
*/
public void deleteMarkers(IMarker[] markers) throws CoreException {
Assert.isNotNull(markers);
if (markers.length == 0)
return;
// clone to avoid outside changes
markers = (IMarker[]) markers.clone();
try {
prepareOperation();
beginOperation(true);
for (int i = 0; i < markers.length; ++i)
if (markers[i] != null && markers[i].getResource() != null)
markerManager.removeMarker(markers[i].getResource(), markers[i].getId());
} finally {
endOperation(false, null);
}
}
/**
* Delete the given resource from the current tree of the receiver.
* This method simply removes the resource from the tree. No cleanup or
* other management is done. Use IResource.delete for proper deletion.
* If the given resource is the root, all of its children (i.e., all projects) are
* deleted but the root is left.
*/
void deleteResource(IResource resource) {
IPath path = resource.getFullPath();
if (path.equals(Path.ROOT)) {
IProject[] children = getRoot().getProjects();
for (int i = 0; i < children.length; i++)
tree.deleteElement(children[i].getFullPath());
} else
tree.deleteElement(path);
}
/**
* Notify the relevant infrastructure pieces that the given project is being
* deleted.
*/
protected void deleting(IProject project) throws CoreException {
buildManager.deleting(project);
natureManager.deleting(project);
notificationManager.deleting(project);
propertyManager.deleting(project);
markerManager.deleting(project);
aliasManager.deleting(project);
}
/**
* Notify relevant infrastructure pieces that a linked resources is being
* deleted. This is needed because the linked resource location is no longer
* available after the operation is completed.
*/
protected void deleting(IResource linkedResource) throws CoreException {
aliasManager.deleting(linkedResource);
}
/**
* For debugging purposes only. Dumps plugin stats to console
*/
public void dumpStats() {
EventStats.dumpStats();
}
/**
* End an operation (group of resource changes).
* Notify interested parties that some resource changes have taken place.
* This is used in the middle of batch executions to broadcast intermediate
* results. All registered resource change listeners are notified. If autobuilding
* is enabled, a build is run.
*/
public void endOperation(boolean build, IProgressMonitor monitor) throws CoreException {
WorkManager workManager = getWorkManager();
try {
workManager.setBuild(build);
// if we are not exiting a top level operation then just decrement the count and return
if (workManager.getPreparedOperationDepth() > 1)
return;
// do the following in a try/finally to ensure that the operation tree is null'd at the end
// as we are completing a top level operation.
try {
// if the tree is locked we likely got here in some finally block after a failed begin.
// Since the tree is locked, nothing could have been done so there is nothing to do.
Assert.isTrue(!(treeLocked && workManager.shouldBuild()), "The tree should not be locked."); //$NON-NLS-1$
// check for a programming error on using beginOperation/endOperation
Assert.isTrue(workManager.getPreparedOperationDepth() > 0, "Mismatched begin/endOperation"); //$NON-NLS-1$
// At this time we need to rebalance the nested operations. It is necessary because
// build() and snapshot() should not fail if they are called.
workManager.rebalanceNestedOperations();
// If autobuild is on, give each open project a chance to build. We have to tell each one
// because there is no way of knowing whether or not there is a relevant change
// for the project without computing the delta for each builder in each project relative
// to its last built state. If we have guaranteed corelation between the notification delta
// and the last time autobuild was done, then we could look at the notification delta and
// see which projects had changed and only build them. Currently there is no such
// guarantee.
// Note that building a project when there is actually nothing to do is not free but
// is should not be too expensive. The computed delta will be empty and so the builder itself
// will not actually be run. This does require however the delta computation.
//
// This is done in a try finally to ensure that we always decrement the operation count.
// The operationCount cannot be decremented before this as the build must be done
// inside an operation. Note that we only ever get here if we are at a top level operation.
// As such, the operationCount will always be 0 (zero) after this.
OperationCanceledException cancel = null;
CoreException signal = null;
monitor = Policy.monitorFor(monitor);
monitor.subTask(Policy.bind("resources.updating")); //$NON-NLS-1$
broadcastChanges(tree, IResourceChangeEvent.PRE_AUTO_BUILD, false, false, Policy.monitorFor(null));
if (isAutoBuilding() && shouldBuild()) {
try {
getBuildManager().build(IncrementalProjectBuilder.AUTO_BUILD, monitor);
} catch (OperationCanceledException e) {
cancel = e;
} catch (CoreException sig) {
signal = sig;
}
}
broadcastChanges(tree, IResourceChangeEvent.POST_AUTO_BUILD, false, false, Policy.monitorFor(null));
broadcastChanges(tree, IResourceChangeEvent.POST_CHANGE, true, true, Policy.monitorFor(null));
getMarkerManager().resetMarkerDeltas();
// Perform a snapshot if we are sufficiently out of date. Be sure to make the tree immutable first
tree.immutable();
saveManager.snapshotIfNeeded();
//make sure the monitor subtask message is cleared.
monitor.subTask(""); //$NON-NLS-1$
if (cancel != null)
throw cancel;
if (signal != null)
throw signal;
} finally {
// make sure that the tree is immutable. Only do this if we are ending a top-level operation.
tree.immutable();
operationTree = null;
}
} finally {
workManager.checkOut();
}
}
/**
* Flush the build order cache for the workspace. Only needed if the
* description does not already have a build order. That is, if this
* is really a cache.
*/
protected void flushBuildOrder() {
if (description.getBuildOrder(false) == null)
buildOrder = null;
}
/**
* @see IWorkspace
*/
public void forgetSavedTree(String pluginId) {
Assert.isNotNull(pluginId, "PluginId must not be null"); //$NON-NLS-1$
saveManager.forgetSavedTree(pluginId);
}
/**
* Returns this workspace's build manager
*/
public BuildManager getBuildManager() {
return buildManager;
}
/**
* Returns the order in which open projects in this workspace will be built.
* <p>
* The project build order is based on information specified in the workspace
* description. The projects are built in the order specified by
* <code>IWorkspaceDescription.getBuildOrder</code>; closed or non-existent
* projects are ignored and not included in the result. If
* <code>IWorkspaceDescription.getBuildOrder</code> is non-null, the default
* build order is used; again, only open projects are included in the result.
* </p>
* <p>
* The returned value is cached in the <code>buildOrder</code> field.
* </p>
*
* @return the list of currently open projects in the workspace in the order in
* which they would be built by <code>IWorkspace.build</code>.
* @see IWorkspace#build
* @see IWorkspaceDescription#getBuildOrder
* @since 2.1
*/
public IProject[] getBuildOrder() {
if (buildOrder != null) {
// return previously-computed and cached project build order
return buildOrder;
}
// see if a particular build order is specified
String[] order = description.getBuildOrder(false);
if (order != null) {
// convert from project names to project handles
// and eliminate non-existent and closed projects
List projectList = new ArrayList(order.length);
for (int i = 0; i < order.length; i++) {
IProject project = getRoot().getProject(order[i]);
//FIXME should non-accessible projects be removed?
if (project.isAccessible()) {
projectList.add(project);
}
}
buildOrder = new IProject[projectList.size()];
projectList.toArray(buildOrder);
} else {
// use default project build order
// computed for all accessible projects in workspace
buildOrder = computeFullProjectOrder().projects;
}
return buildOrder;
}
/**
* @see IWorkspace#getDanglingReferences
*/
public Map getDanglingReferences() {
IProject[] projects = getRoot().getProjects();
Map result = new HashMap(projects.length);
for (int i = 0; i < projects.length; i++) {
Project project = (Project) projects[i];
if (!project.isAccessible())
continue;
IProject[] refs = project.internalGetDescription().getReferencedProjects(false);
List dangling = new ArrayList(refs.length);
for (int j = 0; j < refs.length; j++)
if (!refs[i].exists())
dangling.add(refs[i]);
if (!dangling.isEmpty())
result.put(projects[i], dangling.toArray(new IProject[dangling.size()]));
}
return result;
}
/**
* @see IWorkspace
*/
public IWorkspaceDescription getDescription() {
return (IWorkspaceDescription) description.clone();
}
/**
* Returns the current element tree for this workspace
*/
public ElementTree getElementTree() {
return tree;
}
public FileSystemResourceManager getFileSystemManager() {
return fileSystemManager;
}
/**
* Returns the marker manager for this workspace
*/
public MarkerManager getMarkerManager() {
return markerManager;
}
public LocalMetaArea getMetaArea() {
return localMetaArea;
}
protected IMoveDeleteHook getMoveDeleteHook() {
if (moveDeleteHook == null)
initializeMoveDeleteHook();
return moveDeleteHook;
}
/**
* @see IWorkspace#getNatureDescriptor(String)
*/
public IProjectNatureDescriptor getNatureDescriptor(String natureId) {
return natureManager.getNatureDescriptor(natureId);
}
/**
* @see IWorkspace#getNatureDescriptors()
*/
public IProjectNatureDescriptor[] getNatureDescriptors() {
return natureManager.getNatureDescriptors();
}
/**
* Returns the nature manager for this workspace.
*/
public NatureManager getNatureManager() {
return natureManager;
}
public NotificationManager getNotificationManager() {
return notificationManager;
}
/**
* @see org.eclipse.core.resources.IWorkspace#getPathVariableManager()
*/
public IPathVariableManager getPathVariableManager() {
return pathVariableManager;
}
public PropertyManager getPropertyManager() {
return propertyManager;
}
/**
* Returns the resource info for the identified resource.
* null is returned if no such resource can be found.
* If the phantom flag is true, phantom resources are considered.
* If the mutable flag is true, the info is opened for change.
*
* This method DOES NOT throw an exception if the resource is not found.
*/
public ResourceInfo getResourceInfo(IPath path, boolean phantom, boolean mutable) {
try {
if (path.segmentCount() == 0) {
ResourceInfo info = (ResourceInfo)tree.getTreeData();
Assert.isNotNull(info, "Tree root info must never be null"); //$NON-NLS-1$
return info;
}
ResourceInfo result = null;
if (!tree.includes(path))
return null;
if (mutable)
result = (ResourceInfo) tree.openElementData(path);
else
result = (ResourceInfo) tree.getElementData(path);
if (result != null && (!phantom && result.isSet(M_PHANTOM)))
return null;
return result;
} catch (IllegalArgumentException e) {
return null;
}
}
/**
* @see IWorkspace#getRoot
*/
public IWorkspaceRoot getRoot() {
return defaultRoot;
}
public SaveManager getSaveManager() {
return saveManager;
}
/**
* @see IWorkspace#getSynchronizer
*/
public ISynchronizer getSynchronizer() {
return synchronizer;
}
/**
* Returns the installed team hook. Never returns null.
*/
protected TeamHook getTeamHook() {
if (teamHook == null)
initializeTeamHook();
return teamHook;
}
/**
* We should not have direct references to this field. All references should go through
* this method.
*/
public WorkManager getWorkManager() throws CoreException {
if (workManager == null) {
String message = Policy.bind("resources.shutdown"); //$NON-NLS-1$
throw new ResourceException(new ResourceStatus(IResourceStatus.INTERNAL_ERROR, null, message));
}
return workManager;
}
/**
* A file modification validator hasn't been initialized. Check the extension point and
* try to create a new validator if a user has one defined as an extension.
*/
protected void initializeValidator() {
shouldValidate = false;
IConfigurationElement[] configs = Platform.getPluginRegistry().getConfigurationElementsFor(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_FILE_MODIFICATION_VALIDATOR);
// no-one is plugged into the extension point so disable validation
if (configs == null || configs.length == 0) {
return;
}
// can only have one defined at a time. log a warning, disable validation, but continue with
// the #setContents (e.g. don't throw an exception)
if (configs.length > 1) {
//XXX: shoud provide a meaningful status code
IStatus status = new ResourceStatus(IResourceStatus.ERROR, 1, null, Policy.bind("resources.oneValidator"), null); //$NON-NLS-1$
ResourcesPlugin.getPlugin().getLog().log(status);
return;
}
// otherwise we have exactly one validator extension. Try to create a new instance
// from the user-specified class.
try {
IConfigurationElement config = configs[0];
validator = (IFileModificationValidator) config.createExecutableExtension("class"); //$NON-NLS-1$
shouldValidate = true;
} catch (CoreException e) {
//XXX: shoud provide a meaningful status code
IStatus status = new ResourceStatus(IResourceStatus.ERROR, 1, null, Policy.bind("resources.initValidator"), e); //$NON-NLS-1$
ResourcesPlugin.getPlugin().getLog().log(status);
}
}
/**
* A move/delete hook hasn't been initialized. Check the extension point and
* try to create a new hook if a user has one defined as an extension. Otherwise
* use the Core's implementation as the default.
*/
protected void initializeMoveDeleteHook() {
try {
IConfigurationElement[] configs = Platform.getPluginRegistry().getConfigurationElementsFor(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_MOVE_DELETE_HOOK);
// no-one is plugged into the extension point so disable validation
if (configs == null || configs.length == 0) {
return;
}
// can only have one defined at a time. log a warning
if (configs.length > 1) {
//XXX: shoud provide a meaningful status code
IStatus status = new ResourceStatus(IResourceStatus.ERROR, 1, null, Policy.bind("resources.oneHook"), null); //$NON-NLS-1$
ResourcesPlugin.getPlugin().getLog().log(status);
return;
}
// otherwise we have exactly one hook extension. Try to create a new instance
// from the user-specified class.
try {
IConfigurationElement config = configs[0];
moveDeleteHook = (IMoveDeleteHook) config.createExecutableExtension("class"); //$NON-NLS-1$
} catch (CoreException e) {
//XXX: shoud provide a meaningful status code
IStatus status = new ResourceStatus(IResourceStatus.ERROR, 1, null, Policy.bind("resources.initHook"), e); //$NON-NLS-1$
ResourcesPlugin.getPlugin().getLog().log(status);
}
} finally {
// for now just use Core's implementation
if (moveDeleteHook == null)
moveDeleteHook = new MoveDeleteHook();
}
}
/**
* A team hook hasn't been initialized. Check the extension point and
* try to create a new hook if a user has one defined as an extension.
* Otherwise use the Core's implementation as the default.
*/
protected void initializeTeamHook() {
try {
IConfigurationElement[] configs = Platform.getPluginRegistry().getConfigurationElementsFor(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_TEAM_HOOK);
// no-one is plugged into the extension point so disable validation
if (configs == null || configs.length == 0) {
return;
}
// can only have one defined at a time. log a warning
if (configs.length > 1) {
//XXX: shoud provide a meaningful status code
IStatus status = new ResourceStatus(IResourceStatus.ERROR, 1, null, Policy.bind("resources.oneTeamHook"), null); //$NON-NLS-1$
ResourcesPlugin.getPlugin().getLog().log(status);
return;
}
// otherwise we have exactly one hook extension. Try to create a new instance
// from the user-specified class.
try {
IConfigurationElement config = configs[0];
teamHook = (TeamHook) config.createExecutableExtension("class"); //$NON-NLS-1$
} catch (CoreException e) {
//XXX: shoud provide a meaningful status code
IStatus status = new ResourceStatus(IResourceStatus.ERROR, 1, null, Policy.bind("resources.initTeamHook"), e); //$NON-NLS-1$
ResourcesPlugin.getPlugin().getLog().log(status);
}
} finally {
// default to use Core's implementation
if (teamHook == null)
teamHook = new TeamHook();
}
}
public WorkspaceDescription internalGetDescription() {
return description;
}
/**
* @see IWorkspace
*/
public boolean isAutoBuilding() {
return description.isAutoBuilding();
}
/**
* Returns true if the object at the specified position has any
* other copy in the given array.
*/
private static boolean isDuplicate(Object[] array, int position) {
if (array == null || position >= array.length)
return false;
for (int j = position - 1; j >= 0; j--)
if (array[j].equals(array[position]))
return true;
return false;
}
/**
* @see IWorkspace#isOpen
*/
public boolean isOpen() {
return openFlag;
}
/**
* Returns true if the given file system locations overlap (they are the same,
* or one is a proper prefix of the other), and false otherwise.
* Does the right thing with respect to case insensitive platforms.
*/
protected boolean isOverlapping(IPath location1, IPath location2) {
IPath one = location1;
IPath two = location2;
// If we are on a case-insensitive file system then convert to all lowercase.
if (!CoreFileSystemLibrary.isCaseSensitive()) {
one = new Path(location1.toOSString().toLowerCase());
two = new Path(location2.toOSString().toLowerCase());
}
return one.isPrefixOf(two) || two.isPrefixOf(one);
}
public boolean isTreeLocked() {
return treeLocked;
}
/**
* Link the given tree into the receiver's tree at the specified resource.
*/
protected void linkTrees(IPath path, ElementTree[] newTrees) throws CoreException {
tree = tree.mergeDeltaChain(path, newTrees);
}
/**
* @see IWorkspace#loadProjectDescription
* @since 2.0
*/
public IProjectDescription loadProjectDescription(IPath path) throws CoreException {
IProjectDescription result = null;
IOException e = null;
try {
result = (IProjectDescription) new ModelObjectReader().read(path);
if (result != null) {
// check to see if we are using in the default area or not. use java.io.File for
// testing equality because it knows better w.r.t. drives and case sensitivity
IPath user = path.removeLastSegments(1);
IPath platform = Platform.getLocation().append(result.getName());
if (!user.toFile().equals(platform.toFile()))
result.setLocation(user);
}
} catch (IOException ex) {
e = ex;
}
if (result == null || e != null) {
String message = Policy.bind("resources.errorReadProject", path.toOSString());//$NON-NLS1 //$NON-NLS-1$
IStatus status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, message, e);
throw new ResourceException(status);
}
return result;
}
/*
* @see IWorkspace#move
*/
public IStatus move(IResource[] resources, IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException {
monitor = Policy.monitorFor(monitor);
try {
int opWork = Math.max(resources.length, 1);
int totalWork = Policy.totalWork * opWork / Policy.opWork;
String message = Policy.bind("resources.moving.0"); //$NON-NLS-1$
monitor.beginTask(message, totalWork);
Assert.isLegal(resources != null);
if (resources.length == 0)
return ResourceStatus.OK_STATUS;
resources = (IResource[]) resources.clone(); // to avoid concurrent changes to this array
IPath parentPath = null;
message = Policy.bind("resources.moveProblem"); //$NON-NLS-1$
MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null);
try {
prepareOperation();
beginOperation(true);
for (int i = 0; i < resources.length; i++) {
Policy.checkCanceled(monitor);
Resource resource = (Resource) resources[i];
if (resource == null || isDuplicate(resources, i)) {
monitor.worked(1);
continue;
}
// test siblings
if (parentPath == null)
parentPath = resource.getFullPath().removeLastSegments(1);
if (parentPath.equals(resource.getFullPath().removeLastSegments(1))) {
// test move requirements
try {
IStatus requirements = resource.checkMoveRequirements(destination.append(resource.getName()), resource.getType(), updateFlags);
if (requirements.isOK()) {
try {
resource.move(destination.append(resource.getName()), updateFlags, Policy.subMonitorFor(monitor, 1));
} catch (CoreException e) {
status.merge(e.getStatus());
}
} else {
monitor.worked(1);
status.merge(requirements);
}
} catch (CoreException e) {
monitor.worked(1);
status.merge(e.getStatus());
}
} else {
monitor.worked(1);
message = Policy.bind("resources.notChild", resource.getFullPath().toString(), parentPath.toString()); //$NON-NLS-1$
status.merge(new ResourceStatus(IResourceStatus.OPERATION_FAILED, resource.getFullPath(), message));
}
}
} catch (OperationCanceledException e) {
getWorkManager().operationCanceled();
throw e;
} finally {
endOperation(true, Policy.subMonitorFor(monitor, totalWork - opWork));
}
if (status.matches(IStatus.ERROR))
throw new ResourceException(status);
return status.isOK() ? (IStatus) ResourceStatus.OK_STATUS : (IStatus) status;
} finally {
monitor.done();
}
}
/**
* @see IWorkspace#move
*/
public IStatus move(IResource[] resources, IPath destination, boolean force, IProgressMonitor monitor) throws CoreException {
int updateFlags = force ? IResource.FORCE : IResource.NONE;
updateFlags |= IResource.KEEP_HISTORY;
return move(resources, destination, updateFlags, monitor);
}
/* package */ void move(Resource source, IPath destination, int depth, boolean overwrite) throws CoreException {
move(source, destination, depth, overwrite, false);
}
/**
* Moves this resource's subtree to the destination. This operation should only be
* used by move methods. Destination must be a valid destination for this resource.
* The keepSyncInfo boolean is used to indicated whether or not the sync info should
* be moved from the source to the destination.
*/
/* package */ void move(Resource source, IPath destination, int depth, boolean overwrite, boolean keepSyncInfo) throws CoreException {
// overlay the tree at the destination path, preserving any important info
// in any already existing resource infos
copyTree(source, destination, depth, false, overwrite, keepSyncInfo);
source.fixupAfterMoveSource();
}
/**
* Notify the relevant infrastructure pieces that the given project or linked
* resource is being moved.
*/
protected void moving(IResource source, IResource destination, int updateFlags) {
//most infrastructure only cares about project deletion
if (source.getType() == IResource.PROJECT) {
IProject project = (IProject)source;
buildManager.deleting(project);
natureManager.deleting(project);
notificationManager.deleting(project);
propertyManager.deleting(project);
markerManager.deleting(project);
}
aliasManager.moving(source, destination, updateFlags);
}
/**
* Create and return a new tree element of the given type.
*/
protected ResourceInfo newElement(int type) {
ResourceInfo result = null;
switch (type) {
case IResource.FILE :
case IResource.FOLDER :
result = new ResourceInfo();
break;
case IResource.PROJECT :
result = new ProjectInfo();
break;
case IResource.ROOT :
result = new RootInfo();
break;
}
result.setNodeId(nextNodeId());
result.setModificationStamp(nextModificationStamp());
result.setType(type);
return result;
}
/**
* @see IWorkspace#newProjectDescription
*/
public IProjectDescription newProjectDescription(String projectName) {
IProjectDescription result = new ProjectDescription();
result.setName(projectName);
return result;
}
public Resource newResource(IPath path, int type) {
String message;
switch (type) {
case IResource.FOLDER :
message = "Path must include project and resource name."; //$NON-NLS-1$
Assert.isLegal(path.segmentCount() >= ICoreConstants.MINIMUM_FOLDER_SEGMENT_LENGTH , message);
return new Folder(path.makeAbsolute(), this);
case IResource.FILE :
message = "Path must include project and resource name."; //$NON-NLS-1$
Assert.isLegal(path.segmentCount() >= ICoreConstants.MINIMUM_FILE_SEGMENT_LENGTH, message);
return new File(path.makeAbsolute(), this);
case IResource.PROJECT :
return (Resource) getRoot().getProject(path.lastSegment());
case IResource.ROOT :
return (Resource) getRoot();
}
Assert.isLegal(false);
// will never get here because of assertion.
return null;
}
/**
* Opens a new mutable element tree layer, thus allowing
* modifications to the tree.
*/
public ElementTree newWorkingTree() {
tree = tree.newEmptyDelta();
return tree;
}
/**
* Returns the next, previously unassigned, marker id.
*/
protected long nextMarkerId() {
return nextMarkerId++;
}
public long nextModificationStamp() {
return nextModificationStamp++;
}
public long nextNodeId() {
return nextNodeId++;
}
/**
* Opens this workspace using the data at its location in the local file system.
* This workspace must not be open.
* If the operation succeeds, the result will detail any serious
* (but non-fatal) problems encountered while opening the workspace.
* The status code will be <code>OK</code> if there were no problems.
* An exception is thrown if there are fatal problems opening the workspace,
* in which case the workspace is left closed.
* <p>
* This method is long-running; progress and cancellation are provided
* by the given progress monitor.
* </p>
*
* @param monitor a progress monitor, or <code>null</code> if progress
* reporting and cancellation are not desired
* @return status with code <code>OK</code> if no problems;
* otherwise status describing any serious but non-fatal problems.
*
* @exception CoreException if the workspace could not be opened.
* Reasons include:
* <ul>
* <li> There is no valid workspace structure at the given location
* in the local file system.</li>
* <li> The workspace structure on disk appears to be hopelessly corrupt.</li>
* </ul>
* @see IWorkspace#getLocation
* @see ResourcePlugin#containsWorkspace
*/
public IStatus open(IProgressMonitor monitor) throws CoreException {
// This method is not inside an operation because it is the one responsible for
// creating the WorkManager object (who takes care of operations).
String message = Policy.bind("resources.workspaceOpen"); //$NON-NLS-1$
Assert.isTrue(!isOpen(), message);
description = getMetaArea().readWorkspace();
if (description == null) {
message = Policy.bind("resources.readWorkspaceMeta"); //$NON-NLS-1$
throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, Platform.getLocation(), message, null);
}
// create root location
localMetaArea.locationFor(getRoot()).toFile().mkdirs();
// turn off autobuilding while we open the workspace. This is in
// case an operation is triggered. We don't want to do an autobuild
// just yet. Any changes will be reported the next time we build.
boolean oldBuildFlag = description.isAutoBuilding();
try {
description.setAutoBuilding(false);
IProgressMonitor nullMonitor = Policy.monitorFor(null);
startup(nullMonitor);
//restart the notification manager so it is initialized with the right tree
notificationManager.startup(null);
openFlag = true;
if (crashed || refreshRequested()) {
try {
getRoot().refreshLocal(IResource.DEPTH_INFINITE, null);
} catch (CoreException e) {
//don't fail entire open if refresh failed, just report as minor warning
return e.getStatus();
}
}
return ResourceStatus.OK_STATUS;
} finally {
description.setAutoBuilding(oldBuildFlag);
}
}
/**
* Notify the relevant infrastructure pieces that the given project is being
* opened.
*/
protected void opening(IProject project) throws CoreException {
buildManager.opening(project);
natureManager.opening(project);
notificationManager.opening(project);
propertyManager.opening(project);
markerManager.opening(project);
aliasManager.opening(project);
}
/**
* Called before checking the pre-conditions of an operation.
*/
public void prepareOperation() throws CoreException {
getWorkManager().checkIn();
if (!isOpen()) {
String message = Policy.bind("resources.workspaceClosed"); //$NON-NLS-1$
throw new ResourceException(IResourceStatus.OPERATION_FAILED, null, message, null);
}
}
protected boolean refreshRequested() {
String[] args = Platform.getCommandLineArgs();
for (int i = 0; i < args.length; i++)
if (args[i].equalsIgnoreCase(REFRESH_ON_STARTUP))
return true;
return false;
}
/**
* @see IWorkspace
*/
public void removeResourceChangeListener(IResourceChangeListener listener) {
notificationManager.removeListener(listener);
}
/**
* @see IWorkspace
*/
public void removeSaveParticipant(Plugin plugin) {
Assert.isNotNull(plugin, "Plugin must not be null"); //$NON-NLS-1$
saveManager.removeParticipant(plugin);
}
/**
* @see IWorkspace#run
*/
public void run(IWorkspaceRunnable job, IProgressMonitor monitor) throws CoreException {
monitor = Policy.monitorFor(monitor);
try {
monitor.beginTask(null, Policy.totalWork);
try {
prepareOperation();
beginOperation(true);
job.run(Policy.subMonitorFor(monitor, Policy.opWork, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK));
} catch (OperationCanceledException e) {
getWorkManager().operationCanceled();
throw e;
} finally {
endOperation(false, Policy.subMonitorFor(monitor, Policy.buildWork));
}
} finally {
monitor.done();
}
}
/**
* @see IWorkspace
*/
public IStatus save(boolean full, IProgressMonitor monitor) throws CoreException {
String message;
// if a full save was requested, and this is a top-level op, try the save. Otherwise
// fail the operation.
if (full) {
// If the workmanager thread is null or is different than this thread
// it is OK to start the save because it will wait until the other thread
// is finished. Otherwise, someone in this thread has tried to do a save
// inside of an operation (which is not allowed by the spec).
if (getWorkManager().getCurrentOperationThread() == Thread.currentThread()) {
message = Policy.bind("resources.saveOp"); //$NON-NLS-1$
throw new ResourceException(IResourceStatus.OPERATION_FAILED, null, message, null);
}
return saveManager.save(ISaveContext.FULL_SAVE, null, monitor);
}
// A snapshot was requested. Start an operation (if not already started) and
// signal that a snapshot should be done at the end.
try {
prepareOperation();
beginOperation(false);
saveManager.requestSnapshot();
message = Policy.bind("resources.snapRequest"); //$NON-NLS-1$
return new ResourceStatus(IResourceStatus.OK, message);
} finally {
endOperation(false, null);
}
}
public void setCrashed(boolean value) {
crashed = value;
}
/**
* @see IWorkspace
*/
public void setDescription(IWorkspaceDescription value) throws CoreException {
// if both the old and new description's build orders are null, leave the
// workspace's build order slot because it is caching the computed order.
// Otherwise, set the slot to null to force recomputation or building from the description.
String[] newOrder = ((WorkspaceDescription) value).getBuildOrder(false);
if (description.getBuildOrder(false) != null || newOrder != null)
buildOrder = null;
description = (WorkspaceDescription) ((WorkspaceDescription) value).clone();
Policy.setupAutoBuildProgress(description.isAutoBuilding());
localMetaArea.write(description);
}
public void setTreeLocked(boolean locked) {
treeLocked = locked;
}
public void setWorkspaceLock(WorkspaceLock lock) {
workManager.setWorkspaceLock(lock);
}
private boolean shouldBuild() throws CoreException {
return getWorkManager().shouldBuild() && ElementTree.hasChanges(tree, operationTree, ResourceComparator.getComparator(false), true);
}
protected void shutdown(IProgressMonitor monitor) throws CoreException {
monitor = Policy.monitorFor(monitor);
try {
IManager[] managers = { buildManager, notificationManager, propertyManager, pathVariableManager, fileSystemManager, markerManager, saveManager, workManager };
monitor.beginTask(null, managers.length);
String message = Policy.bind("resources.shutdownProblems"); //$NON-NLS-1$
MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null);
// best effort to shutdown every object and free resources
for (int i = 0; i < managers.length; i++) {
IManager manager = managers[i];
if (manager == null)
monitor.worked(1);
else {
try {
manager.shutdown(Policy.subMonitorFor(monitor, 1));
} catch (Exception e) {
message = Policy.bind("resources.shutdownProblems"); //$NON-NLS-1$
status.add(new Status(Status.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, e));
}
}
}
buildManager = null;
notificationManager = null;
propertyManager = null;
pathVariableManager = null;
fileSystemManager = null;
markerManager = null;
synchronizer = null;
saveManager = null;
workManager = null;
if (!status.isOK())
throw new CoreException(status);
} finally {
monitor.done();
}
}
/**
* @see IWorkspace#sortNatureSet(String[])
*/
public String[] sortNatureSet(String[] natureIds) {
return natureManager.sortNatureSet(natureIds);
}
protected void startup(IProgressMonitor monitor) throws CoreException {
// ensure the tree is locked during the startup notification
workManager = new WorkManager(this);
workManager.startup(null);
fileSystemManager = new FileSystemResourceManager(this);
fileSystemManager.startup(monitor);
propertyManager = new PropertyManager(this);
propertyManager.startup(monitor);
pathVariableManager = new PathVariableManager(this);
pathVariableManager.startup(null);
natureManager = new NatureManager();
natureManager.startup(null);
buildManager = new BuildManager(this);
buildManager.startup(null);
notificationManager = new NotificationManager(this);
notificationManager.startup(null);
markerManager = new MarkerManager(this);
markerManager.startup(null);
synchronizer = new Synchronizer(this);
saveManager = new SaveManager(this);
saveManager.startup(null);
//must start after save manager, because (read) access to tree is needed
aliasManager = new AliasManager(this);
aliasManager.startup(null);
treeLocked = false; // unlock the tree.
}
/**
* Returns a string representation of this working state's
* structure suitable for debug purposes.
*/
public String toDebugString() {
final StringBuffer buffer = new StringBuffer("\nDump of " + toString() + ":\n"); //$NON-NLS-1$ //$NON-NLS-2$
buffer.append(" parent: " + tree.getParent()); //$NON-NLS-1$
ElementTreeIterator iterator = new ElementTreeIterator();
IElementContentVisitor visitor = new IElementContentVisitor() {
public void visitElement(ElementTree tree, IPath path, Object elementContents) {
buffer.append("\n " + path + ": " + elementContents); //$NON-NLS-1$ //$NON-NLS-2$
}
};
iterator.iterate(tree, visitor);
return buffer.toString();
}
public void updateModificationStamp(ResourceInfo info) {
info.setModificationStamp(nextModificationStamp());
}
/* (non-javadoc)
* Method declared on IWorkspace.
*/
public IStatus validateEdit(final IFile[] files, final Object context) {
// if validation is turned off then just return
if (!shouldValidate) {
String message = Policy.bind("resources.readOnly2"); //$NON-NLS-1$
MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IStatus.OK, message, null);
for (int i=0; i<files.length; i++) {
if (files[i].isReadOnly()) {
IPath filePath = files[i].getFullPath();
message = Policy.bind("resources.readOnly", filePath.toString()); //$NON-NLS-1$
result.add(new ResourceStatus(IResourceStatus.FAILED_WRITE_LOCAL, filePath, message));
}
}
return result.isOK() ? ResourceStatus.OK_STATUS : (IStatus) result;
}
// first time through the validator hasn't been initialized so try and create it
if (validator == null)
initializeValidator();
// we were unable to initialize the validator. Validation has been turned off and
// a warning has already been logged so just return.
if (validator == null)
return ResourceStatus.OK_STATUS;
// otherwise call the API and throw an exception if appropriate
final IStatus[] status = new IStatus[1];
ISafeRunnable body = new ISafeRunnable() {
public void run() throws Exception {
status[0] = validator.validateEdit(files, context);
}
public void handleException(Throwable exception) {
status[0] = new ResourceStatus(IResourceStatus.ERROR, null, Policy.bind("resources.errorValidator"), exception); //$NON-NLS-1$
}
};
Platform.run(body);
return status[0];
}
/* (non-javadoc)
* Method declared on IWorkspace.
*/
public IStatus validateLinkLocation(IResource resource, IPath location) {
//check the standard path name restrictions
int segmentCount = location.segmentCount();
for (int i = 0; i < segmentCount; i++) {
IStatus result = validateName(location.segment(i), resource.getType());
if (!result.isOK())
return result;
}
//if the location doesn't have a device, see if the OS will assign one
if (location.getDevice() == null)
location = new Path(location.toFile().getAbsolutePath());
// test if the given location overlaps the platform metadata location
IPath testLocation = getMetaArea().getLocation();
String message;
if (isOverlapping(location, testLocation)) {
message = Policy.bind("links.invalidLocation", location.toString()); //$NON-NLS-1$
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
//test if the given path overlaps the location of the given project
testLocation = resource.getProject().getLocation();
if (isOverlapping(location, testLocation)) {
message = Policy.bind("links.locationOverlapsProject", location.toString()); //$NON-NLS-1$
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
// Iterate over each known project and ensure that the location does not
// conflict with any project locations or linked resource locations
IProject[] projects = getRoot().getProjects();
for (int i = 0; i < projects.length; i++) {
IProject project = (IProject) projects[i];
// since we are iterating over the project in the workspace, we
// know that they have been created before and must have a description
IProjectDescription desc = ((Project) project).internalGetDescription();
testLocation = desc.getLocation();
// if the project uses the default location then continue
if (testLocation == null)
continue;
if (isOverlapping(location, testLocation)) {
message = Policy.bind("links.overlappingResource", location.toString()); //$NON-NLS-1$
return new ResourceStatus(IResourceStatus.OVERLAPPING_LOCATION, null, message);
}
//iterate over linked resources and check for overlap
if (!project.isOpen())
continue;
IResource[] children = null;
try {
children = project.members();
} catch (CoreException e) {
//ignore projects that cannot be accessed
}
if (children == null)
continue;
for (int j = 0; j < children.length; j++) {
if (children[j].isLinked()) {
testLocation = children[j].getLocation();
if (isOverlapping(location, testLocation)) {
message = Policy.bind("links.overlappingResource", location.toString()); //$NON-NLS-1$
return new ResourceStatus(IResourceStatus.OVERLAPPING_LOCATION, null, message);
}
}
}
}
return ResourceStatus.OK_STATUS;
}
/**
* @see IWorkspace#validateName
*/
public IStatus validateName(String segment, int type) {
String message;
/* segment must not be null */
if (segment == null) {
message = Policy.bind("resources.nameNull"); //$NON-NLS-1$
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
// cannot be an empty string
if (segment.length() == 0) {
message = Policy.bind("resources.nameEmpty"); //$NON-NLS-1$
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
/* segment must not begin or end with a whitespace */
if (Character.isWhitespace(segment.charAt(0)) || Character.isWhitespace(segment.charAt(segment.length() - 1))) {
message = Policy.bind("resources.invalidWhitespace",segment); //$NON-NLS-1$
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
/* segment must not end with a dot */
if (segment.endsWith(".")) { //$NON-NLS-1$
message = Policy.bind("resources.invalidDot", segment); //$NON-NLS-1$
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
/* test invalid characters */
char[] chars = OS.INVALID_RESOURCE_CHARACTERS;
for (int i = 0; i < chars.length; i++)
if (segment.indexOf(chars[i]) != -1) {
message = Policy.bind("resources.invalidCharInName", String.valueOf(chars[i]), segment); //$NON-NLS-1$
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
/* test invalid OS names */
if (!OS.isNameValid(segment)) {
message = Policy.bind("resources.invalidName", segment); //$NON-NLS-1$
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
return ResourceStatus.OK_STATUS;
}
/**
* @see IWorkspace#validateNatureSet(String[])
*/
public IStatus validateNatureSet(String[] natureIds) {
return natureManager.validateNatureSet(natureIds);
}
/**
* @see IWorkspace#validatePath
*/
public IStatus validatePath(String path, int type) {
String message;
/* path must not be null */
if (path == null) {
message = Policy.bind("resources.pathNull"); //$NON-NLS-1$
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
/* path must not have a device separator */
if (path.indexOf(IPath.DEVICE_SEPARATOR) != -1) {
message = Policy.bind("resources.invalidCharInPath", String.valueOf(IPath.DEVICE_SEPARATOR), path); //$NON-NLS-1$
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
/* path must not be the root path */
IPath testPath = new Path(path);
if (testPath.isRoot()) {
message = Policy.bind("resources.invalidRoot"); //$NON-NLS-1$
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
/* path must be absolute */
if (!testPath.isAbsolute()) {
message = Policy.bind("resources.mustBeAbsolute", path); //$NON-NLS-1$
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
/* validate segments */
int numberOfSegments = testPath.segmentCount();
if ((type & IResource.PROJECT) != 0) {
if (numberOfSegments == ICoreConstants.PROJECT_SEGMENT_LENGTH) {
return validateName(testPath.segment(0), IResource.PROJECT);
} else
if (type == IResource.PROJECT) {
message = Policy.bind("resources.projectPath",path); //$NON-NLS-1$
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
}
if ((type & (IResource.FILE | IResource.FOLDER)) != 0)
if (numberOfSegments >= ICoreConstants.MINIMUM_FILE_SEGMENT_LENGTH) {
IStatus status = validateName(testPath.segment(0), IResource.PROJECT);
if (!status.isOK())
return status;
int fileFolderType = type &= ~IResource.PROJECT;
int segmentCount = testPath.segmentCount();
// ignore first segment (the project)
for (int i = 1; i < segmentCount; i++) {
status = validateName(testPath.segment(i), fileFolderType);
if (!status.isOK())
return status;
}
return ResourceStatus.OK_STATUS;
} else {
message = Policy.bind("resources.resourcePath",path); //$NON-NLS-1$
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
message = Policy.bind("resources.invalidPath", path); //$NON-NLS-1$
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
/**
* @see IWorkspace#validateProjectLocation
*/
public IStatus validateProjectLocation(IProject context, IPath location) {
String message;
// the default default is ok for all projects
if (location == null) {
message = Policy.bind("resources.validLocation"); //$NON-NLS-1$
return new ResourceStatus(IResourceStatus.OK, message);
}
//check the standard path name restrictions
int segmentCount = location.segmentCount();
for (int i = 0; i < segmentCount; i++) {
IStatus result = validateName(location.segment(i), IResource.PROJECT);
if (!result.isOK())
return result;
}
//if the location doesn't have a device, see if the OS will assign one
if (location.getDevice() == null)
location = new Path(location.toFile().getAbsolutePath());
// test if the given location overlaps the default default location
IPath defaultDefaultLocation = Platform.getLocation();
if (isOverlapping(location, defaultDefaultLocation)) {
message = Policy.bind("resources.overlapLocal", location.toString(), defaultDefaultLocation.toString()); //$NON-NLS-1$
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
// Iterate over each known project and ensure that the location does not
// conflict with any of their already defined locations.
IProject[] projects = getRoot().getProjects();
for (int j = 0; j < projects.length; j++) {
IProject project = (IProject) projects[j];
// since we are iterating over the project in the workspace, we
// know that they have been created before and must have a description
IProjectDescription desc = ((Project) project).internalGetDescription();
IPath definedLocalLocation = desc.getLocation();
// if the project uses the default location then continue
if (definedLocalLocation == null)
continue;
//tolerate locations being the same if this is the project being tested
if (project.equals(context) && definedLocalLocation.equals(location))
continue;
if (isOverlapping(location, definedLocalLocation)) {
message = Policy.bind("resources.overlapLocal", location.toString(), definedLocalLocation.toString()); //$NON-NLS-1$
return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
}
}
message = Policy.bind("resources.validLocation"); //$NON-NLS-1$
return new ResourceStatus(IResourceStatus.OK, message);
}
/**
* Internal method. To be called only from the following methods:
* <ul>
* <li><code>IFile#appendContents</code></li>
* <li><code>IFile#setContents(InputStream, boolean, boolean, IProgressMonitor)</code></li>
* <li><code>IFile#setContents(IFileState, boolean, boolean, IProgressMonitor)</code></li>
* </ul>
*
* @see IFileModificationValidator#validateSave
*/
protected void validateSave(final IFile file) throws CoreException {
// if validation is turned off then just return
if (!shouldValidate)
return;
// first time through the validator hasn't been initialized so try and create it
if (validator == null)
initializeValidator();
// we were unable to initialize the validator. Validation has been turned off and
// a warning has already been logged so just return.
if (validator == null)
return;
// otherwise call the API and throw an exception if appropriate
final IStatus[] status = new IStatus[1];
ISafeRunnable body = new ISafeRunnable() {
public void run() throws Exception {
status[0] = validator.validateSave(file);
}
public void handleException(Throwable exception) {
status[0] = new ResourceStatus(IResourceStatus.ERROR, null, Policy.bind("resources.errorValidator"), exception); //$NON-NLS-1$
}
};
Platform.run(body);
if (!status[0].isOK())
throw new ResourceException(status[0]);
}
}