platform35/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/ResourceTree.java
changeset 40 eb3c938c7fef
equal deleted inserted replaced
39:2a03ec4dbf31 40:eb3c938c7fef
       
     1 /*******************************************************************************
       
     2  * Copyright (c) 2000, 2008 IBM Corporation and others.
       
     3  * All rights reserved. This program and the accompanying materials
       
     4  * are made available under the terms of the Eclipse Public License v1.0
       
     5  * which accompanies this distribution, and is available at
       
     6  * http://www.eclipse.org/legal/epl-v10.html
       
     7  * 
       
     8  * Contributors:
       
     9  *     IBM Corporation - initial API and implementation
       
    10  *******************************************************************************/
       
    11 package org.eclipse.core.internal.resources;
       
    12 
       
    13 import java.net.URI;
       
    14 import org.eclipse.core.filesystem.*;
       
    15 import org.eclipse.core.filesystem.URIUtil;
       
    16 import org.eclipse.core.internal.localstore.FileSystemResourceManager;
       
    17 import org.eclipse.core.internal.properties.IPropertyManager;
       
    18 import org.eclipse.core.internal.utils.*;
       
    19 import org.eclipse.core.resources.*;
       
    20 import org.eclipse.core.resources.team.IResourceTree;
       
    21 import org.eclipse.core.runtime.*;
       
    22 import org.eclipse.core.runtime.jobs.ILock;
       
    23 import org.eclipse.osgi.util.NLS;
       
    24 
       
    25 /**
       
    26  * @since 2.0
       
    27  * 
       
    28  * Implementation note: Since the move/delete hook involves running third
       
    29  * party code, the workspace lock is not held.  This means the workspace
       
    30  * lock must be re-acquired whenever we need to manipulate the workspace
       
    31  * in any way.  All entry points from third party code back into the tree must
       
    32  * be done in an acquire/release pair.
       
    33  */
       
    34 class ResourceTree implements IResourceTree {
       
    35 
       
    36 	private boolean isValid = true;
       
    37 	private final FileSystemResourceManager localManager;
       
    38 	/**
       
    39 	 * The lock to acquire when the workspace needs to be manipulated
       
    40 	 */
       
    41 	private ILock lock;
       
    42 	private MultiStatus multistatus;
       
    43 	private int updateFlags;
       
    44 
       
    45 	/**
       
    46 	 * Constructor for this class.
       
    47 	 */
       
    48 	public ResourceTree(FileSystemResourceManager localManager, ILock lock, MultiStatus status, int updateFlags) {
       
    49 		super();
       
    50 		this.localManager = localManager;
       
    51 		this.lock = lock;
       
    52 		this.multistatus = status;
       
    53 		this.updateFlags = updateFlags;
       
    54 	}
       
    55 
       
    56 	/**
       
    57 	 * @see IResourceTree#addToLocalHistory(IFile)
       
    58 	 */
       
    59 	public void addToLocalHistory(IFile file) {
       
    60 		Assert.isLegal(isValid);
       
    61 		try {
       
    62 			lock.acquire();
       
    63 			if (!file.exists())
       
    64 				return;
       
    65 			IFileStore store = localManager.getStore(file);
       
    66 			final IFileInfo fileInfo = store.fetchInfo();
       
    67 			if (!fileInfo.exists())
       
    68 				return;
       
    69 			localManager.getHistoryStore().addState(file.getFullPath(), store, fileInfo, false);
       
    70 		} finally {
       
    71 			lock.release();
       
    72 		}
       
    73 	}
       
    74 
       
    75 	private IFileStore computeDestinationStore(IProjectDescription destDescription) throws CoreException {
       
    76 		URI destLocation = destDescription.getLocationURI();
       
    77 		// Use the default area if necessary for the destination.
       
    78 		if (destLocation == null) {
       
    79 			IPath rootLocation = ResourcesPlugin.getWorkspace().getRoot().getLocation();
       
    80 			destLocation = rootLocation.append(destDescription.getName()).toFile().toURI();
       
    81 		}
       
    82 		return EFS.getStore(destLocation);
       
    83 	}
       
    84 
       
    85 	/**
       
    86 	 * @see IResourceTree#computeTimestamp(IFile)
       
    87 	 */
       
    88 	public long computeTimestamp(IFile file) {
       
    89 		Assert.isLegal(isValid);
       
    90 		try {
       
    91 			lock.acquire();
       
    92 			if (!file.getProject().exists())
       
    93 				return NULL_TIMESTAMP;
       
    94 			return internalComputeTimestamp(file);
       
    95 		} finally {
       
    96 			lock.release();
       
    97 		}
       
    98 	}
       
    99 
       
   100 	/**
       
   101 	 * Copies the local history of source to destination.  Note that if source
       
   102 	 * is an IFolder, it is assumed that the same structure exists under destination
       
   103 	 * and the local history of any IFile under source will be copied to the
       
   104 	 * associated IFile under destination.
       
   105 	 */
       
   106 	private void copyLocalHistory(IResource source, IResource destination) {
       
   107 		localManager.getHistoryStore().copyHistory(source, destination, true);
       
   108 	}
       
   109 
       
   110 	/**
       
   111 	 * @see IResourceTree#deletedFile(IFile)
       
   112 	 */
       
   113 	public void deletedFile(IFile file) {
       
   114 		Assert.isLegal(isValid);
       
   115 		try {
       
   116 			lock.acquire();
       
   117 			// Do nothing if the resource doesn't exist.
       
   118 			if (!file.exists())
       
   119 				return;
       
   120 			try {
       
   121 				// Delete properties, generate marker deltas, and remove the node from the workspace tree.
       
   122 				((Resource) file).deleteResource(true, null);
       
   123 			} catch (CoreException e) {
       
   124 				String message = NLS.bind(Messages.resources_errorDeleting, file.getFullPath());
       
   125 				IStatus status = new ResourceStatus(IStatus.ERROR, file.getFullPath(), message, e);
       
   126 				failed(status);
       
   127 			}
       
   128 		} finally {
       
   129 			lock.release();
       
   130 		}
       
   131 	}
       
   132 
       
   133 	/**
       
   134 	 * @see IResourceTree#deletedFolder(IFolder)
       
   135 	 */
       
   136 	public void deletedFolder(IFolder folder) {
       
   137 		Assert.isLegal(isValid);
       
   138 		try {
       
   139 			lock.acquire();
       
   140 			// Do nothing if the resource doesn't exist.
       
   141 			if (!folder.exists())
       
   142 				return;
       
   143 			try {
       
   144 				// Delete properties, generate marker deltas, and remove the node from the workspace tree.
       
   145 				((Resource) folder).deleteResource(true, null);
       
   146 			} catch (CoreException e) {
       
   147 				String message = NLS.bind(Messages.resources_errorDeleting, folder.getFullPath());
       
   148 				IStatus status = new ResourceStatus(IStatus.ERROR, folder.getFullPath(), message, e);
       
   149 				failed(status);
       
   150 			}
       
   151 		} finally {
       
   152 			lock.release();
       
   153 		}
       
   154 	}
       
   155 
       
   156 	/**
       
   157 	 * @see IResourceTree#deletedProject(IProject)
       
   158 	 */
       
   159 	public void deletedProject(IProject target) {
       
   160 		Assert.isLegal(isValid);
       
   161 		try {
       
   162 			lock.acquire();
       
   163 			// Do nothing if the resource doesn't exist.
       
   164 			if (!target.exists())
       
   165 				return;
       
   166 			// Delete properties, generate marker deltas, and remove the node from the workspace tree.
       
   167 			try {
       
   168 				((Project) target).deleteResource(false, null);
       
   169 			} catch (CoreException e) {
       
   170 				String message = NLS.bind(Messages.resources_errorDeleting, target.getFullPath());
       
   171 				IStatus status = new ResourceStatus(IStatus.ERROR, target.getFullPath(), message, e);
       
   172 				// log the status but don't return until we try and delete the rest of the project info
       
   173 				failed(status);
       
   174 			}
       
   175 		} finally {
       
   176 			lock.release();
       
   177 		}
       
   178 	}
       
   179 
       
   180 	/**
       
   181 	 * Makes sure that the destination directory for a project move is unoccupied.
       
   182 	 * Returns true if successful, and false if the move should be aborted
       
   183 	 */
       
   184 	private boolean ensureDestinationEmpty(IProject source, IFileStore destinationStore, IProgressMonitor monitor) throws CoreException {
       
   185 		String message;
       
   186 		//Make sure the destination location is unoccupied
       
   187 		if (!destinationStore.fetchInfo().exists())
       
   188 			return true;
       
   189 		//check for existing children
       
   190 		if (destinationStore.childNames(EFS.NONE, Policy.subMonitorFor(monitor, 0)).length > 0) {
       
   191 			//allow case rename to proceed
       
   192 			if (((Resource) source).getStore().equals(destinationStore))
       
   193 				return true;
       
   194 			//fail because the destination is occupied
       
   195 			message = NLS.bind(Messages.localstore_resourceExists, destinationStore);
       
   196 			IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, null);
       
   197 			failed(status);
       
   198 			return false;
       
   199 		}
       
   200 		//delete the destination directory to allow for efficient renaming
       
   201 		destinationStore.delete(EFS.NONE, Policy.subMonitorFor(monitor, 0));
       
   202 		return true;
       
   203 	}
       
   204 
       
   205 	/**
       
   206 	 * This operation has failed for the given reason. Add it to this
       
   207 	 * resource tree's status.
       
   208 	 */
       
   209 	public void failed(IStatus reason) {
       
   210 		Assert.isLegal(isValid);
       
   211 		multistatus.add(reason);
       
   212 	}
       
   213 
       
   214 	/**
       
   215 	 * Returns the status object held onto by this resource tree.
       
   216 	 */
       
   217 	protected IStatus getStatus() {
       
   218 		return multistatus;
       
   219 	}
       
   220 
       
   221 	/**
       
   222 	 * @see IResourceTree#getTimestamp(IFile)
       
   223 	 */
       
   224 	public long getTimestamp(IFile file) {
       
   225 		Assert.isLegal(isValid);
       
   226 		try {
       
   227 			lock.acquire();
       
   228 			if (!file.exists())
       
   229 				return NULL_TIMESTAMP;
       
   230 			ResourceInfo info = ((File) file).getResourceInfo(false, false);
       
   231 			return info == null ? NULL_TIMESTAMP : info.getLocalSyncInfo();
       
   232 		} finally {
       
   233 			lock.release();
       
   234 		}
       
   235 	}
       
   236 
       
   237 	/**
       
   238 	 * Returns the local timestamp for a file.
       
   239 	 * 
       
   240 	 * @param file
       
   241 	 * @return The local file system timestamp
       
   242 	 */
       
   243 	private long internalComputeTimestamp(IFile file) {
       
   244 		IFileInfo fileInfo = localManager.getStore(file).fetchInfo();
       
   245 		return fileInfo.exists() ? fileInfo.getLastModified() : NULL_TIMESTAMP;
       
   246 	}
       
   247 
       
   248 	/**
       
   249 	 * Helper method for #standardDeleteFile. Returns a boolean indicating whether or
       
   250 	 * not the delete was successful. 
       
   251 	 */
       
   252 	private boolean internalDeleteFile(IFile file, int flags, IProgressMonitor monitor) {
       
   253 		try {
       
   254 			String message = NLS.bind(Messages.resources_deleting, file.getFullPath());
       
   255 			monitor.beginTask(message, Policy.totalWork);
       
   256 			Policy.checkCanceled(monitor);
       
   257 
       
   258 			// Do nothing if the file doesn't exist in the workspace.
       
   259 			if (!file.exists()) {
       
   260 				// Indicate that the delete was successful.
       
   261 				return true;
       
   262 			}
       
   263 			// Don't delete contents if this is a linked resource
       
   264 			if (file.isLinked()) {
       
   265 				deletedFile(file);
       
   266 				return true;
       
   267 			}
       
   268 			// If the file doesn't exist on disk then signal to the workspace to delete the
       
   269 			// file and return.
       
   270 			IFileStore fileStore = localManager.getStore(file);
       
   271 			boolean localExists = fileStore.fetchInfo().exists();
       
   272 			if (!localExists) {
       
   273 				deletedFile(file);
       
   274 				// Indicate that the delete was successful.
       
   275 				return true;
       
   276 			}
       
   277 
       
   278 			boolean keepHistory = (flags & IResource.KEEP_HISTORY) != 0;
       
   279 			boolean force = (flags & IResource.FORCE) != 0;
       
   280 
       
   281 			// Add the file to the local history if requested by the user.
       
   282 			if (keepHistory)
       
   283 				addToLocalHistory(file);
       
   284 			monitor.worked(Policy.totalWork / 4);
       
   285 
       
   286 			// We want to fail if force is false and the file is not synchronized with the 
       
   287 			// local file system.
       
   288 			if (!force) {
       
   289 				boolean inSync = isSynchronized(file, IResource.DEPTH_ZERO);
       
   290 				// only want to fail if the file still exists.
       
   291 				if (!inSync && localExists) {
       
   292 					message = NLS.bind(Messages.localstore_resourceIsOutOfSync, file.getFullPath());
       
   293 					IStatus status = new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, file.getFullPath(), message);
       
   294 					failed(status);
       
   295 					// Indicate that the delete was unsuccessful.
       
   296 					return false;
       
   297 				}
       
   298 			}
       
   299 			monitor.worked(Policy.totalWork / 4);
       
   300 
       
   301 			// Try to delete the file from the file system.
       
   302 			try {
       
   303 				fileStore.delete(EFS.NONE, Policy.subMonitorFor(monitor, Policy.totalWork / 4));
       
   304 				// If the file was successfully deleted from the file system the
       
   305 				// workspace tree should be updated accordingly.
       
   306 				deletedFile(file);
       
   307 				// Indicate that the delete was successful.
       
   308 				return true;
       
   309 			} catch (CoreException e) {
       
   310 				message = NLS.bind(Messages.resources_couldnotDelete, fileStore.toString());
       
   311 				IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, file.getFullPath(), message, e);
       
   312 				failed(status);
       
   313 			}
       
   314 			// Indicate that the delete was unsuccessful.
       
   315 			return false;
       
   316 		} finally {
       
   317 			monitor.done();
       
   318 		}
       
   319 	}
       
   320 
       
   321 	/**
       
   322 	 * Helper method for #standardDeleteFolder. Returns a boolean indicating 
       
   323 	 * whether or not the deletion of this folder was successful. Does a best effort
       
   324 	 * delete of this resource and its children.
       
   325 	 */
       
   326 	private boolean internalDeleteFolder(IFolder folder, int flags, IProgressMonitor monitor) {
       
   327 		String message = NLS.bind(Messages.resources_deleting, folder.getFullPath());
       
   328 		monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$
       
   329 		monitor.subTask(message);
       
   330 		Policy.checkCanceled(monitor);
       
   331 
       
   332 		// Do nothing if the folder doesn't exist in the workspace.
       
   333 		if (!folder.exists())
       
   334 			return true;
       
   335 
       
   336 		// Don't delete contents if this is a linked resource
       
   337 		if (folder.isLinked()) {
       
   338 			deletedFolder(folder);
       
   339 			return true;
       
   340 		}
       
   341 
       
   342 		// If the folder doesn't exist on disk then update the tree and return.
       
   343 		IFileStore fileStore = localManager.getStore(folder);
       
   344 		if (!fileStore.fetchInfo().exists()) {
       
   345 			deletedFolder(folder);
       
   346 			return true;
       
   347 		}
       
   348 
       
   349 		try {
       
   350 			//this will delete local and workspace
       
   351 			localManager.delete(folder, flags, Policy.subMonitorFor(monitor, Policy.totalWork));
       
   352 		} catch (CoreException ce) {
       
   353 			message = NLS.bind(Messages.localstore_couldnotDelete, folder.getFullPath());
       
   354 			IStatus status = new ResourceStatus(IStatus.ERROR, IResourceStatus.FAILED_DELETE_LOCAL, folder.getFullPath(), message, ce);		
       
   355 			failed(status);
       
   356 			return false;
       
   357 		}
       
   358 		return true;
       
   359 	}
       
   360 
       
   361 	/**
       
   362 	 * Does a best-effort delete on this resource and all its children.
       
   363 	 */
       
   364 	private boolean internalDeleteProject(IProject project, int flags, IProgressMonitor monitor) {
       
   365 		// Recursively delete each member of the project.
       
   366 		IResource[] members = null;
       
   367 		try {
       
   368 			members = project.members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN);
       
   369 		} catch (CoreException e) {
       
   370 			String message = NLS.bind(Messages.resources_errorMembers, project.getFullPath());
       
   371 			IStatus status = new ResourceStatus(IStatus.ERROR, project.getFullPath(), message, e);
       
   372 			failed(status);
       
   373 			// Indicate that the delete was unsuccessful.
       
   374 			return false;
       
   375 		}
       
   376 		boolean deletedChildren = true;
       
   377 		for (int i = 0; i < members.length; i++) {
       
   378 			IResource child = members[i];
       
   379 			switch (child.getType()) {
       
   380 				case IResource.FILE :
       
   381 					// ignore the .project file for now and delete it last
       
   382 					if (!IProjectDescription.DESCRIPTION_FILE_NAME.equals(child.getName()))
       
   383 						deletedChildren &= internalDeleteFile((IFile) child, flags, Policy.subMonitorFor(monitor, Policy.totalWork / members.length));
       
   384 					break;
       
   385 				case IResource.FOLDER :
       
   386 					deletedChildren &= internalDeleteFolder((IFolder) child, flags, Policy.subMonitorFor(monitor, Policy.totalWork / members.length));
       
   387 					break;
       
   388 			}
       
   389 		}
       
   390 		IFileStore projectStore = localManager.getStore(project);
       
   391 		// Check to see if the children were deleted ok. If there was a problem
       
   392 		// just return as the problem should have been logged by the recursive
       
   393 		// call to the child.
       
   394 		if (!deletedChildren)
       
   395 			// Indicate that the delete was unsuccessful.
       
   396 			return false;
       
   397 
       
   398 		//Check if there are any undiscovered children of the project on disk other than description file
       
   399 		String[] children;
       
   400 		try {
       
   401 			children = projectStore.childNames(EFS.NONE, null);
       
   402 		} catch (CoreException e) {
       
   403 			//treat failure to access the directory as a non-existent directory
       
   404 			children = new String[0];
       
   405 		}
       
   406 		boolean force = BitMask.isSet(flags, IResource.FORCE);
       
   407 		if (!force && (children.length != 1 || !IProjectDescription.DESCRIPTION_FILE_NAME.equals(children[0]))) {
       
   408 			String message = NLS.bind(Messages.localstore_resourceIsOutOfSync, project.getName());
       
   409 			failed(new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, project.getFullPath(), message));
       
   410 			return false;
       
   411 		}
       
   412 
       
   413 		//Now delete the project description file
       
   414 		IResource file = project.findMember(IProjectDescription.DESCRIPTION_FILE_NAME);
       
   415 		if (file == null) {
       
   416 			//the .project have may have been recreated on disk automatically by snapshot
       
   417 			IFileStore dotProject = projectStore.getChild(IProjectDescription.DESCRIPTION_FILE_NAME);
       
   418 			try {
       
   419 				dotProject.delete(EFS.NONE, null);
       
   420 			} catch (CoreException e) {
       
   421 				failed(e.getStatus());
       
   422 			}
       
   423 		} else {
       
   424 			boolean deletedProjectFile = internalDeleteFile((IFile) file, flags, Policy.monitorFor(null));
       
   425 			if (!deletedProjectFile) {
       
   426 				String message = NLS.bind(Messages.resources_couldnotDelete, file.getFullPath());
       
   427 				IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, file.getFullPath(), message);
       
   428 				failed(status);
       
   429 				// Indicate that the delete was unsuccessful.
       
   430 				return false;
       
   431 			}
       
   432 		}
       
   433 
       
   434 		//children are deleted, so now delete the parent
       
   435 		try {
       
   436 			projectStore.delete(EFS.NONE, null);
       
   437 			deletedProject(project);
       
   438 			// Indicate that the delete was successful.
       
   439 			return true;
       
   440 		} catch (CoreException e) {
       
   441 			String message = NLS.bind(Messages.resources_couldnotDelete, projectStore.toString());
       
   442 			IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message, e);
       
   443 			failed(status);
       
   444 			// Indicate that the delete was unsuccessful.
       
   445 			return false;
       
   446 		}
       
   447 	}
       
   448 
       
   449 	/**
       
   450 	 * Return <code>true</code> if there is a change in the content area for the project.
       
   451 	 */
       
   452 	private boolean isContentChange(IProject project, IProjectDescription destDescription) {
       
   453 		IProjectDescription srcDescription = ((Project) project).internalGetDescription();
       
   454 		URI srcLocation = srcDescription.getLocationURI();
       
   455 		URI destLocation = destDescription.getLocationURI();
       
   456 		if (srcLocation == null || destLocation == null)
       
   457 			return true;
       
   458 		//don't use URIUtil because we want to treat case rename as a content change
       
   459 		return !srcLocation.equals(destLocation);
       
   460 	}
       
   461 
       
   462 	/**
       
   463 	 * Return <code>true</code> if there is a change in the name of the project.
       
   464 	 */
       
   465 	private boolean isNameChange(IProject project, IProjectDescription description) {
       
   466 		return !project.getName().equals(description.getName());
       
   467 	}
       
   468 	
       
   469 	/**
       
   470 	 * Refreshes the resource hierarchy with its children. In case of failure
       
   471 	 * adds an appropriate status to the resource tree's status.
       
   472 	 */
       
   473 	private void safeRefresh(IResource resource) {
       
   474 		try {
       
   475 			resource.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
       
   476 		} catch (CoreException ce) {
       
   477 			IStatus status = new ResourceStatus(IStatus.ERROR, IResourceStatus.FAILED_DELETE_LOCAL, resource.getFullPath(), Messages.refresh_refreshErr, ce);			
       
   478 			failed(status);
       
   479 		}
       
   480 	}
       
   481 
       
   482 	/**
       
   483 	 * @see IResourceTree#isSynchronized(IResource, int)
       
   484 	 */
       
   485 	public boolean isSynchronized(IResource resource, int depth) {
       
   486 		try {
       
   487 			lock.acquire();
       
   488 			return localManager.isSynchronized(resource, depth);
       
   489 		} finally {
       
   490 			lock.release();
       
   491 		}
       
   492 	}
       
   493 
       
   494 	/**
       
   495 	 * The specific operation for which this tree was created has completed and this tree
       
   496 	 * should not be used anymore. Ensure that this is the case by making it invalid. This
       
   497 	 * is checked by all API methods.
       
   498 	 */
       
   499 	void makeInvalid() {
       
   500 		this.isValid = false;
       
   501 	}
       
   502 
       
   503 	/**
       
   504 	 * @see IResourceTree#movedFile(IFile, IFile)
       
   505 	 */
       
   506 	public void movedFile(IFile source, IFile destination) {
       
   507 		Assert.isLegal(isValid);
       
   508 		try {
       
   509 			lock.acquire();
       
   510 			// Do nothing if the resource doesn't exist.
       
   511 			if (!source.exists())
       
   512 				return;
       
   513 			// If the destination already exists then we have a problem.
       
   514 			if (destination.exists()) {
       
   515 				String message = NLS.bind(Messages.resources_mustNotExist, destination.getFullPath());
       
   516 				IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message);
       
   517 				// log the status but don't return until we try and move the rest of the resource information.
       
   518 				failed(status);
       
   519 			}
       
   520 
       
   521 			// Move the resource's persistent properties.
       
   522 			IPropertyManager propertyManager = ((Resource) source).getPropertyManager();
       
   523 			try {
       
   524 				propertyManager.copy(source, destination, IResource.DEPTH_ZERO);
       
   525 				propertyManager.deleteProperties(source, IResource.DEPTH_ZERO);
       
   526 			} catch (CoreException e) {
       
   527 				String message = NLS.bind(Messages.resources_errorPropertiesMove, source.getFullPath(), destination.getFullPath());
       
   528 				IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e);
       
   529 				// log the status but don't return until we try and move the rest of the resource information.
       
   530 				failed(status);
       
   531 			}
       
   532 
       
   533 			// Move the node in the workspace tree.
       
   534 			Workspace workspace = (Workspace) source.getWorkspace();
       
   535 			try {
       
   536 				workspace.move((Resource) source, destination.getFullPath(), IResource.DEPTH_ZERO, updateFlags, false);
       
   537 			} catch (CoreException e) {
       
   538 				String message = NLS.bind(Messages.resources_errorMoving, source.getFullPath(), destination.getFullPath());
       
   539 				IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e);
       
   540 				// log the status but don't return until we try and move the rest of the resource information.
       
   541 				failed(status);
       
   542 			}
       
   543 
       
   544 			// Generate the marker deltas.
       
   545 			try {
       
   546 				workspace.getMarkerManager().moved(source, destination, IResource.DEPTH_ZERO);
       
   547 			} catch (CoreException e) {
       
   548 				String message = NLS.bind(Messages.resources_errorMarkersDelete, source.getFullPath());
       
   549 				IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e);
       
   550 				failed(status);
       
   551 			}
       
   552 
       
   553 			// Copy the local history information
       
   554 			copyLocalHistory(source, destination);
       
   555 		} finally {
       
   556 			lock.release();
       
   557 		}
       
   558 	}
       
   559 
       
   560 	/**
       
   561 	 * @see IResourceTree#movedFolderSubtree(IFolder, IFolder)
       
   562 	 */
       
   563 	public void movedFolderSubtree(IFolder source, IFolder destination) {
       
   564 		Assert.isLegal(isValid);
       
   565 		try {
       
   566 			lock.acquire();
       
   567 			// Do nothing if the source resource doesn't exist.
       
   568 			if (!source.exists())
       
   569 				return;
       
   570 			// If the destination already exists then we have an error.
       
   571 			if (destination.exists()) {
       
   572 				String message = NLS.bind(Messages.resources_mustNotExist, destination.getFullPath());
       
   573 				IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message);
       
   574 				failed(status);
       
   575 				return;
       
   576 			}
       
   577 
       
   578 			// Move the folder properties.
       
   579 			int depth = IResource.DEPTH_INFINITE;
       
   580 			IPropertyManager propertyManager = ((Resource) source).getPropertyManager();
       
   581 			try {
       
   582 				propertyManager.copy(source, destination, depth);
       
   583 				propertyManager.deleteProperties(source, depth);
       
   584 			} catch (CoreException e) {
       
   585 				String message = NLS.bind(Messages.resources_errorPropertiesMove, source.getFullPath(), destination.getFullPath());
       
   586 				IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e);
       
   587 				// log the status but don't return until we try and move the rest of the resource info
       
   588 				failed(status);
       
   589 			}
       
   590 
       
   591 			// Create the destination node in the tree.
       
   592 			Workspace workspace = (Workspace) source.getWorkspace();
       
   593 			try {
       
   594 				workspace.move((Resource) source, destination.getFullPath(), depth, updateFlags, false);
       
   595 			} catch (CoreException e) {
       
   596 				String message = NLS.bind(Messages.resources_errorMoving, source.getFullPath(), destination.getFullPath());
       
   597 				IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e);
       
   598 				// log the status but don't return until we try and move the rest of the resource info
       
   599 				failed(status);
       
   600 			}
       
   601 
       
   602 			// Generate the marker deltas.
       
   603 			try {
       
   604 				workspace.getMarkerManager().moved(source, destination, depth);
       
   605 			} catch (CoreException e) {
       
   606 				String message = NLS.bind(Messages.resources_errorMarkersDelete, source.getFullPath());
       
   607 				IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e);
       
   608 				failed(status);
       
   609 			}
       
   610 
       
   611 			// Copy the local history for this folder
       
   612 			copyLocalHistory(source, destination);
       
   613 		} finally {
       
   614 			lock.release();
       
   615 		}
       
   616 	}
       
   617 
       
   618 	/**
       
   619 	 * @see IResourceTree#movedProjectSubtree(IProject, IProjectDescription)
       
   620 	 */
       
   621 	public boolean movedProjectSubtree(IProject project, IProjectDescription destDescription) {
       
   622 		Assert.isLegal(isValid);
       
   623 		try {
       
   624 			lock.acquire();
       
   625 			// Do nothing if the source resource doesn't exist.
       
   626 			if (!project.exists())
       
   627 				return true;
       
   628 
       
   629 			Project source = (Project) project;
       
   630 			Project destination = (Project) source.getWorkspace().getRoot().getProject(destDescription.getName());
       
   631 			Workspace workspace = (Workspace) source.getWorkspace();
       
   632 			int depth = IResource.DEPTH_INFINITE;
       
   633 
       
   634 			// If the name of the source and destination projects are not the same then 
       
   635 			// rename the meta area and make changes in the tree.
       
   636 			if (isNameChange(source, destDescription)) {
       
   637 				if (destination.exists()) {
       
   638 					String message = NLS.bind(Messages.resources_mustNotExist, destination.getFullPath());
       
   639 					IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message);
       
   640 					failed(status);
       
   641 					return false;
       
   642 				}
       
   643 
       
   644 				// Rename the project metadata area. Close the property store to flush everything to disk
       
   645 				try {
       
   646 					source.getPropertyManager().closePropertyStore(source);
       
   647 					localManager.getHistoryStore().closeHistoryStore(source);
       
   648 				} catch (CoreException e) {
       
   649 					String message = NLS.bind(Messages.properties_couldNotClose, source.getFullPath());
       
   650 					IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e);
       
   651 					// log the status but don't return until we try and move the rest of the resource info
       
   652 					failed(status);
       
   653 				}
       
   654 				final IFileSystem fileSystem = EFS.getLocalFileSystem();
       
   655 				IFileStore oldMetaArea = fileSystem.getStore(workspace.getMetaArea().locationFor(source));
       
   656 				IFileStore newMetaArea = fileSystem.getStore(workspace.getMetaArea().locationFor(destination));
       
   657 				try {
       
   658 					oldMetaArea.move(newMetaArea, EFS.NONE, new NullProgressMonitor());
       
   659 				} catch (CoreException e) {
       
   660 					String message = NLS.bind(Messages.resources_moveMeta, oldMetaArea, newMetaArea);
       
   661 					IStatus status = new ResourceStatus(IResourceStatus.FAILED_WRITE_METADATA, destination.getFullPath(), message, e);
       
   662 					// log the status but don't return until we try and move the rest of the resource info
       
   663 					failed(status);
       
   664 				}
       
   665 
       
   666 				// Move the workspace tree.
       
   667 				try {
       
   668 					workspace.move(source, destination.getFullPath(), depth, updateFlags, true);
       
   669 				} catch (CoreException e) {
       
   670 					String message = NLS.bind(Messages.resources_errorMoving, source.getFullPath(), destination.getFullPath());
       
   671 					IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e);
       
   672 					// log the status but don't return until we try and move the rest of the resource info
       
   673 					failed(status);
       
   674 				}
       
   675 
       
   676 				// Clear stale state on the destination project.
       
   677 				((ProjectInfo) destination.getResourceInfo(false, true)).fixupAfterMove();
       
   678 
       
   679 				// Generate marker deltas.
       
   680 				try {
       
   681 					workspace.getMarkerManager().moved(source, destination, depth);
       
   682 				} catch (CoreException e) {
       
   683 					String message = NLS.bind(Messages.resources_errorMarkersMove, source.getFullPath(), destination.getFullPath());
       
   684 					IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e);
       
   685 					// log the status but don't return until we try and move the rest of the resource info
       
   686 					failed(status);
       
   687 				}
       
   688 				// Copy the local history
       
   689 				copyLocalHistory(source, destination);
       
   690 			}
       
   691 
       
   692 			// Write the new project description on the destination project.
       
   693 			try {
       
   694 				//moving linked resources may have modified the description in memory
       
   695 				((ProjectDescription) destDescription).setLinkDescriptions(destination.internalGetDescription().getLinks());
       
   696 				destination.internalSetDescription(destDescription, true);
       
   697 				destination.writeDescription(IResource.FORCE);
       
   698 			} catch (CoreException e) {
       
   699 				String message = Messages.resources_projectDesc;
       
   700 				IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message, e);
       
   701 				failed(status);
       
   702 			}
       
   703 
       
   704 			// write the private project description, including the project location
       
   705 			try {
       
   706 				workspace.getMetaArea().writePrivateDescription(destination);
       
   707 			} catch (CoreException e) {
       
   708 				failed(e.getStatus());
       
   709 			}
       
   710 
       
   711 			// Do a refresh on the destination project to pick up any newly discovered resources
       
   712 			try {
       
   713 				destination.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
       
   714 			} catch (CoreException e) {
       
   715 				String message = NLS.bind(Messages.resources_errorRefresh, destination.getFullPath());
       
   716 				IStatus status = new ResourceStatus(IStatus.ERROR, destination.getFullPath(), message, e);
       
   717 				failed(status);
       
   718 				return false;
       
   719 			}
       
   720 			return true;
       
   721 		} finally {
       
   722 			lock.release();
       
   723 		}
       
   724 	}
       
   725 
       
   726 	/**
       
   727 	 * Helper method for moving the project content. Determines the content location
       
   728 	 * based on the project description. (default location or user defined?)
       
   729 	 */
       
   730 	private void moveProjectContent(IProject source, IFileStore destStore, int flags, IProgressMonitor monitor) throws CoreException {
       
   731 		try {
       
   732 			String message = NLS.bind(Messages.resources_moving, source.getFullPath());
       
   733 			monitor.beginTask(message, 10);
       
   734 			IProjectDescription srcDescription = source.getDescription();
       
   735 			URI srcLocation = srcDescription.getLocationURI();
       
   736 			// If the locations are the same (and non-default) then there is nothing to do.
       
   737 			if (srcLocation != null && URIUtil.equals(srcLocation, destStore.toURI()))
       
   738 				return;
       
   739 
       
   740 			//If this is a replace, just make sure the destination location exists, and return
       
   741 			boolean replace = (flags & IResource.REPLACE) != 0;
       
   742 			if (replace) {
       
   743 				destStore.mkdir(EFS.NONE, Policy.subMonitorFor(monitor, 10));
       
   744 				return;
       
   745 			}
       
   746 
       
   747 			// Move the contents on disk.
       
   748 			localManager.move(source, destStore, flags, Policy.subMonitorFor(monitor, 9));
       
   749 
       
   750 			//if this is a deep move, move the contents of any linked resources
       
   751 			if ((flags & IResource.SHALLOW) == 0) {
       
   752 				IResource[] children = source.members();
       
   753 				for (int i = 0; i < children.length; i++) {
       
   754 					if (children[i].isLinked()) {
       
   755 						message = NLS.bind(Messages.resources_moving, children[i].getFullPath());
       
   756 						monitor.subTask(message);
       
   757 						IFileStore linkDestination = destStore.getChild(children[i].getName());
       
   758 						try {
       
   759 							localManager.move(children[i], linkDestination, flags, Policy.monitorFor(null));
       
   760 						} catch (CoreException ce) {
       
   761 							//log the failure, but keep trying on remaining links
       
   762 							failed(ce.getStatus());
       
   763 						}
       
   764 					}
       
   765 				}
       
   766 			}
       
   767 			monitor.worked(1);
       
   768 		} finally {
       
   769 			monitor.done();
       
   770 		}
       
   771 	}
       
   772 
       
   773 	/**
       
   774 	 * @see IResourceTree#standardDeleteFile(IFile, int, IProgressMonitor)
       
   775 	 */
       
   776 	public void standardDeleteFile(IFile file, int flags, IProgressMonitor monitor) {
       
   777 		Assert.isLegal(isValid);
       
   778 		try {
       
   779 			lock.acquire();
       
   780 			internalDeleteFile(file, flags, monitor);
       
   781 		} finally {
       
   782 			lock.release();
       
   783 		}
       
   784 	}
       
   785 
       
   786 	/**
       
   787 	 * @see IResourceTree#standardDeleteFolder(IFolder, int, IProgressMonitor)
       
   788 	 */
       
   789 	public void standardDeleteFolder(IFolder folder, int flags, IProgressMonitor monitor) {
       
   790 		Assert.isLegal(isValid);
       
   791 		try {
       
   792 			lock.acquire();
       
   793 			internalDeleteFolder(folder, flags, monitor);
       
   794 		} catch (OperationCanceledException oce) {
       
   795 			safeRefresh(folder);
       
   796 			throw oce;
       
   797 		} finally {
       
   798 			lock.release();
       
   799 			monitor.done();
       
   800 		}
       
   801 	}
       
   802 
       
   803 	/**
       
   804 	 * @see IResourceTree#standardDeleteProject(IProject, int, IProgressMonitor)
       
   805 	 */
       
   806 	public void standardDeleteProject(IProject project, int flags, IProgressMonitor monitor) {
       
   807 		Assert.isLegal(isValid);
       
   808 		try {
       
   809 			lock.acquire();
       
   810 			String message = NLS.bind(Messages.resources_deleting, project.getFullPath());
       
   811 			monitor.beginTask(message, Policy.totalWork);
       
   812 			// Do nothing if the project doesn't exist in the workspace tree.
       
   813 			if (!project.exists())
       
   814 				return;
       
   815 
       
   816 			boolean alwaysDeleteContent = (flags & IResource.ALWAYS_DELETE_PROJECT_CONTENT) != 0;
       
   817 			boolean neverDeleteContent = (flags & IResource.NEVER_DELETE_PROJECT_CONTENT) != 0;
       
   818 			boolean success = true;
       
   819 
       
   820 			// Delete project content.  Don't do anything if the user specified explicitly asked
       
   821 			// not to delete the project content or if the project is closed and
       
   822 			// ALWAYS_DELETE_PROJECT_CONTENT was not specified.
       
   823 			if (alwaysDeleteContent || (project.isOpen() && !neverDeleteContent)) {
       
   824 				// Force is implied if alwaysDeleteContent is true or if the project is in sync 
       
   825 				// with the local file system.
       
   826 				if (alwaysDeleteContent || isSynchronized(project, IResource.DEPTH_INFINITE)) {
       
   827 					flags |= IResource.FORCE;
       
   828 				}
       
   829 					
       
   830 				// If the project is open we have to recursively try and delete all the files doing best-effort.
       
   831 				if (project.isOpen()) {
       
   832 					success = internalDeleteProject(project, flags, monitor);
       
   833 					if (!success) {
       
   834 						IFileStore store = localManager.getStore(project);
       
   835 						message = NLS.bind(Messages.resources_couldnotDelete, store.toString());
       
   836 						IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message);
       
   837 						failed(status);
       
   838 					}
       
   839 					return;
       
   840 				}
       
   841 				
       
   842 				// If the project is closed we can short circuit this operation and delete all the files on disk.
       
   843 				// The .project file is deleted at the end of the operation.
       
   844 				try {
       
   845 					IFileStore projectStore = localManager.getStore(project);
       
   846 					IFileStore members[] = projectStore.childStores(EFS.NONE, null);
       
   847 					for (int i = 0; i < members.length; i++) {
       
   848 						if (!IProjectDescription.DESCRIPTION_FILE_NAME.equals(members[i].getName()))
       
   849 							members[i].delete(EFS.NONE, Policy.subMonitorFor(monitor, Policy.totalWork * 7 / 8 / members.length));
       
   850 					}
       
   851 					projectStore.delete(EFS.NONE, Policy.subMonitorFor(monitor, Policy.totalWork * 7 / 8 / (members.length > 0 ? members.length : 1)));
       
   852 				} catch (OperationCanceledException oce) {
       
   853 					safeRefresh(project);
       
   854 					throw oce;
       
   855 				} catch (CoreException ce) {
       
   856 					message = NLS.bind(Messages.localstore_couldnotDelete, project.getFullPath());
       
   857 					IStatus status = new ResourceStatus(IStatus.ERROR, IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message, ce);
       
   858 					failed(status);
       
   859 					return;
       
   860 				}
       
   861 			}
       
   862 
       
   863 			// Signal that the workspace tree should be updated that the project has been deleted.
       
   864 			if (success)
       
   865 				deletedProject(project);
       
   866 			else {
       
   867 				message = NLS.bind(Messages.localstore_couldnotDelete, project.getFullPath());
       
   868 				IStatus status = new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, project.getFullPath(), message);
       
   869 				failed(status);
       
   870 			}
       
   871 		} finally {
       
   872 			lock.release();
       
   873 			monitor.done();
       
   874 		}
       
   875 	}
       
   876 
       
   877 	/**
       
   878 	 * @see IResourceTree#standardMoveFile(IFile, IFile, int, IProgressMonitor)
       
   879 	 */
       
   880 	public void standardMoveFile(IFile source, IFile destination, int flags, IProgressMonitor monitor) {
       
   881 		Assert.isLegal(isValid);
       
   882 		try {
       
   883 			lock.acquire();
       
   884 			String message = NLS.bind(Messages.resources_moving, source.getFullPath());
       
   885 			monitor.subTask(message);
       
   886 
       
   887 			// These pre-conditions should all be ok but just in case...
       
   888 			if (!source.exists() || destination.exists() || !destination.getParent().isAccessible())
       
   889 				throw new IllegalArgumentException();
       
   890 
       
   891 			boolean force = (flags & IResource.FORCE) != 0;
       
   892 			boolean keepHistory = (flags & IResource.KEEP_HISTORY) != 0;
       
   893 			boolean isDeep = (flags & IResource.SHALLOW) == 0;
       
   894 
       
   895 			// If the file is not in sync with the local file system and force is false,
       
   896 			// then signal that we have an error.
       
   897 			if (!force && !isSynchronized(source, IResource.DEPTH_INFINITE)) {
       
   898 				message = NLS.bind(Messages.localstore_resourceIsOutOfSync, source.getFullPath());
       
   899 				IStatus status = new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, source.getFullPath(), message);
       
   900 				failed(status);
       
   901 				return;
       
   902 			}
       
   903 			monitor.worked(Policy.totalWork / 4);
       
   904 
       
   905 			// Add the file contents to the local history if requested by the user.	
       
   906 			if (keepHistory)
       
   907 				addToLocalHistory(source);
       
   908 			monitor.worked(Policy.totalWork / 4);
       
   909 
       
   910 			//for shallow move of linked resources, nothing needs to be moved in the file system
       
   911 			if (!isDeep && source.isLinked()) {
       
   912 				movedFile(source, destination);
       
   913 				return;
       
   914 			}
       
   915 
       
   916 			// If the file was successfully moved in the file system then the workspace
       
   917 			// tree needs to be updated accordingly. Otherwise signal that we have an error.
       
   918 			IFileStore destStore = null;
       
   919 			boolean failedDeletingSource = false;
       
   920 			try {
       
   921 				destStore = localManager.getStore(destination);
       
   922 				//ensure parent of destination exists
       
   923 				destStore.getParent().mkdir(EFS.NONE, Policy.subMonitorFor(monitor, 0));
       
   924 				localManager.move(source, destStore, flags, monitor);
       
   925 			} catch (CoreException e) {
       
   926 				failed(e.getStatus());
       
   927 				// did the fail occur after copying to the destination?									
       
   928 				failedDeletingSource = destStore != null && destStore.fetchInfo().exists();
       
   929 				// if so, we should proceed
       
   930 				if (!failedDeletingSource)
       
   931 					return;
       
   932 			}
       
   933 			movedFile(source, destination);
       
   934 			updateMovedFileTimestamp(destination, internalComputeTimestamp(destination));
       
   935 			if (failedDeletingSource) {
       
   936 				//recreate source file to ensure we are not out of sync
       
   937 				try {
       
   938 					source.refreshLocal(IResource.DEPTH_INFINITE, null);
       
   939 				} catch (CoreException e) {
       
   940 					//ignore secondary failure - we have already logged the main failure
       
   941 				}
       
   942 			}
       
   943 			monitor.worked(Policy.totalWork / 4);
       
   944 			return;
       
   945 		} finally {
       
   946 			lock.release();
       
   947 			monitor.done();
       
   948 		}
       
   949 	}
       
   950 
       
   951 	/**
       
   952 	 * @see IResourceTree#standardMoveFolder(IFolder, IFolder, int, IProgressMonitor)
       
   953 	 */
       
   954 	public void standardMoveFolder(IFolder source, IFolder destination, int flags, IProgressMonitor monitor) {
       
   955 		Assert.isLegal(isValid);
       
   956 		try {
       
   957 			lock.acquire();
       
   958 			String message = NLS.bind(Messages.resources_moving, source.getFullPath());
       
   959 			monitor.beginTask(message, 100);
       
   960 
       
   961 			// These pre-conditions should all be ok but just in case...
       
   962 			if (!source.exists() || destination.exists() || !destination.getParent().isAccessible())
       
   963 				throw new IllegalArgumentException();
       
   964 
       
   965 			// Check to see if we are synchronized with the local file system. If we are in sync then we can
       
   966 			// short circuit this method and do a file system only move. Otherwise we have to recursively
       
   967 			// try and move all resources, doing it in a best-effort manner.
       
   968 			boolean force = (flags & IResource.FORCE) != 0;
       
   969 			if (!force && !isSynchronized(source, IResource.DEPTH_INFINITE)) {
       
   970 				message = NLS.bind(Messages.localstore_resourceIsOutOfSync, source.getFullPath());
       
   971 				IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message);
       
   972 				failed(status);
       
   973 				return;
       
   974 			}
       
   975 			monitor.worked(20);
       
   976 
       
   977 			//for linked resources, nothing needs to be moved in the file system
       
   978 			boolean isDeep = (flags & IResource.SHALLOW) == 0;
       
   979 			if (!isDeep && source.isLinked()) {
       
   980 				movedFolderSubtree(source, destination);
       
   981 				return;
       
   982 			}
       
   983 
       
   984 			// Move the resources in the file system. Only the FORCE flag is valid here so don't
       
   985 			// have to worry about clearing the KEEP_HISTORY flag.
       
   986 			IFileStore destStore = null;
       
   987 			boolean failedDeletingSource = false;
       
   988 			try {
       
   989 				destStore = localManager.getStore(destination);
       
   990 				localManager.move(source, destStore, flags, Policy.subMonitorFor(monitor, 60));
       
   991 			} catch (CoreException e) {
       
   992 				failed(e.getStatus());
       
   993 				// did the fail occur after copying to the destination?
       
   994 				failedDeletingSource = destStore != null && destStore.fetchInfo().exists();
       
   995 				// if so, we should proceed
       
   996 				if (!failedDeletingSource)
       
   997 					return;
       
   998 			}
       
   999 			movedFolderSubtree(source, destination);
       
  1000 			monitor.worked(20);
       
  1001 			updateTimestamps(destination, isDeep);
       
  1002 			if (failedDeletingSource) {
       
  1003 				//the move could have been partially successful, so refresh to ensure we are in sync
       
  1004 				try {
       
  1005 					source.refreshLocal(IResource.DEPTH_INFINITE, null);
       
  1006 					destination.refreshLocal(IResource.DEPTH_INFINITE, null);
       
  1007 				} catch (CoreException e) {
       
  1008 					//ignore secondary failures -we have already logged main failure
       
  1009 				}
       
  1010 			}
       
  1011 		} finally {
       
  1012 			lock.release();
       
  1013 			monitor.done();
       
  1014 		}
       
  1015 	}
       
  1016 
       
  1017 	/**
       
  1018 	 * @see IResourceTree#standardMoveProject(IProject, IProjectDescription, int, IProgressMonitor)
       
  1019 	 */
       
  1020 	public void standardMoveProject(IProject source, IProjectDescription description, int flags, IProgressMonitor monitor) {
       
  1021 		Assert.isLegal(isValid);
       
  1022 		try {
       
  1023 			lock.acquire();
       
  1024 			String message = NLS.bind(Messages.resources_moving, source.getFullPath());
       
  1025 			monitor.beginTask(message, Policy.totalWork);
       
  1026 
       
  1027 			// Double-check this pre-condition.
       
  1028 			if (!source.isAccessible())
       
  1029 				throw new IllegalArgumentException();
       
  1030 
       
  1031 			// If there is nothing to do on disk then signal to make the workspace tree
       
  1032 			// changes.
       
  1033 			if (!isContentChange(source, description)) {
       
  1034 				movedProjectSubtree(source, description);
       
  1035 				return;
       
  1036 			}
       
  1037 
       
  1038 			// Check to see if we are synchronized with the local file system. 
       
  1039 			boolean force = (flags & IResource.FORCE) != 0;
       
  1040 			if (!force && !isSynchronized(source, IResource.DEPTH_INFINITE)) {
       
  1041 				// FIXME: make this a best effort move?
       
  1042 				message = NLS.bind(Messages.localstore_resourceIsOutOfSync, source.getFullPath());
       
  1043 				IStatus status = new ResourceStatus(IResourceStatus.OUT_OF_SYNC_LOCAL, source.getFullPath(), message);
       
  1044 				failed(status);
       
  1045 				return;
       
  1046 			}
       
  1047 
       
  1048 			IFileStore destinationStore;
       
  1049 			try {
       
  1050 				destinationStore = computeDestinationStore(description);
       
  1051 				//destination can be non-empty on replace
       
  1052 				if ((flags & IResource.REPLACE) == 0)
       
  1053 					if (!ensureDestinationEmpty(source, destinationStore, monitor))
       
  1054 						return;
       
  1055 			} catch (CoreException e) {
       
  1056 				//must fail if the destination location cannot be accessd (undefined file system)
       
  1057 				message = NLS.bind(Messages.localstore_couldNotMove, source.getFullPath());
       
  1058 				IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e);
       
  1059 				failed(status);
       
  1060 				return;
       
  1061 			}
       
  1062 
       
  1063 			// Move the project content in the local file system.
       
  1064 			try {
       
  1065 				moveProjectContent(source, destinationStore, flags, Policy.subMonitorFor(monitor, Policy.totalWork * 3 / 4));
       
  1066 			} catch (CoreException e) {
       
  1067 				message = NLS.bind(Messages.localstore_couldNotMove, source.getFullPath());
       
  1068 				IStatus status = new ResourceStatus(IStatus.ERROR, source.getFullPath(), message, e);
       
  1069 				failed(status);
       
  1070 				//refresh the project because it might have been partially moved
       
  1071 				try {
       
  1072 					source.refreshLocal(IResource.DEPTH_INFINITE, null);
       
  1073 				} catch (CoreException e2) {
       
  1074 					//ignore secondary failures
       
  1075 				}
       
  1076 			}
       
  1077 
       
  1078 			// If we got this far the project content has been moved on disk (if necessary)
       
  1079 			// and we need to update the workspace tree.
       
  1080 			movedProjectSubtree(source, description);
       
  1081 			monitor.worked(Policy.totalWork * 1 / 8);
       
  1082 
       
  1083 			boolean isDeep = (flags & IResource.SHALLOW) == 0;
       
  1084 			updateTimestamps(source.getWorkspace().getRoot().getProject(description.getName()), isDeep);
       
  1085 			monitor.worked(Policy.totalWork * 1 / 8);
       
  1086 		} finally {
       
  1087 			lock.release();
       
  1088 			monitor.done();
       
  1089 		}
       
  1090 	}
       
  1091 
       
  1092 	/**
       
  1093 	 * @see IResourceTree#updateMovedFileTimestamp(IFile, long)
       
  1094 	 */
       
  1095 	public void updateMovedFileTimestamp(IFile file, long timestamp) {
       
  1096 		Assert.isLegal(isValid);
       
  1097 		try {
       
  1098 			lock.acquire();
       
  1099 			// Do nothing if the file doesn't exist in the workspace tree.
       
  1100 			if (!file.exists())
       
  1101 				return;
       
  1102 			// Update the timestamp in the tree.
       
  1103 			ResourceInfo info = ((Resource) file).getResourceInfo(false, true);
       
  1104 			// The info should never be null since we just checked that the resource exists in the tree.
       
  1105 			localManager.updateLocalSync(info, timestamp);
       
  1106 			//remove the linked bit since this resource has been moved in the file system
       
  1107 			info.clear(ICoreConstants.M_LINK);
       
  1108 		} finally {
       
  1109 			lock.release();
       
  1110 		}
       
  1111 	}
       
  1112 
       
  1113 	/**
       
  1114 	 * Helper method to update all the timestamps in the tree to match
       
  1115 	 * those in the file system. Used after a #move.
       
  1116 	 */
       
  1117 	private void updateTimestamps(IResource root, final boolean isDeep) {
       
  1118 		IResourceVisitor visitor = new IResourceVisitor() {
       
  1119 			public boolean visit(IResource resource) {
       
  1120 				if (resource.isLinked()) {
       
  1121 					if (isDeep) {
       
  1122 						//clear the linked resource bit, if any
       
  1123 						ResourceInfo info = ((Resource) resource).getResourceInfo(false, true);
       
  1124 						info.clear(ICoreConstants.M_LINK);
       
  1125 					}
       
  1126 					return true;
       
  1127 				}
       
  1128 				//only needed if underlying file system does not preserve timestamps
       
  1129 				//				if (resource.getType() == IResource.FILE) {
       
  1130 				//					IFile file = (IFile) resource;
       
  1131 				//					updateMovedFileTimestamp(file, computeTimestamp(file));
       
  1132 				//				}
       
  1133 				return true;
       
  1134 			}
       
  1135 		};
       
  1136 		try {
       
  1137 			root.accept(visitor, IResource.DEPTH_INFINITE, IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN);
       
  1138 		} catch (CoreException e) {
       
  1139 			// No exception should be thrown.
       
  1140 		}
       
  1141 	}
       
  1142 }