| /******************************************************************************* |
| * Copyright (c) 2000, 2015 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Serge Beauchamp (Freescale Semiconductor) - [229633] Project Path Variable Support |
| * Anton Leherbauer (Wind River) - [198591] Allow Builder to specify scheduling rule |
| * Francis Lynch (Wind River) - [301563] Save and load tree snapshots |
| * Markus Schorn (Wind River) - [306575] Save snapshot location with project |
| * Broadcom Corporation - ongoing development |
| * Lars Vogel <Lars.Vogel@vogella.com> - Bug 473427 |
| *******************************************************************************/ |
| package org.eclipse.core.internal.resources; |
| |
| import java.net.URI; |
| import java.util.*; |
| import org.eclipse.core.filesystem.*; |
| import org.eclipse.core.internal.events.LifecycleEvent; |
| import org.eclipse.core.internal.utils.*; |
| import org.eclipse.core.resources.*; |
| import org.eclipse.core.resources.team.IMoveDeleteHook; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.core.runtime.content.IContentTypeMatcher; |
| import org.eclipse.core.runtime.jobs.ISchedulingRule; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.osgi.util.NLS; |
| |
| public class Project extends Container implements IProject { |
| /** |
| * Option constant (value 2) indicating that the snapshot location shall be |
| * persisted with the project for autoloading the snapshot when the project |
| * is imported in another workspace. |
| * <p> |
| * <strong>EXPERIMENTAL</strong>. This constant has been added as |
| * part of a work in progress. There is no guarantee that this API will |
| * work or that it will remain the same. Please do not use this API without |
| * consulting with the Platform Core team. |
| * </p> |
| * @see #saveSnapshot(int, URI, IProgressMonitor) |
| * @since 3.6 |
| */ |
| public static final int SNAPSHOT_SET_AUTOLOAD = 2; |
| |
| protected Project(IPath path, Workspace container) { |
| super(path, container); |
| } |
| |
| protected void assertCreateRequirements(IProjectDescription description) throws CoreException { |
| checkDoesNotExist(); |
| checkDescription(this, description, false); |
| URI location = description.getLocationURI(); |
| if (location != null) |
| return; |
| //if the project is in the default location, need to check for collision with existing folder of different case |
| if (!Workspace.caseSensitive) { |
| IFileStore store = getStore(); |
| IFileInfo localInfo = store.fetchInfo(); |
| if (localInfo.exists()) { |
| String name = getLocalManager().getLocalName(store); |
| if (name != null && !store.getName().equals(name)) { |
| String msg = NLS.bind(Messages.resources_existsLocalDifferentCase, new Path(store.toString()).removeLastSegments(1).append(name).toOSString()); |
| throw new ResourceException(IResourceStatus.CASE_VARIANT_EXISTS, getFullPath(), msg, null); |
| } |
| } |
| } |
| } |
| |
| /* |
| * If the creation boolean is true then this method is being called on project creation. |
| * Otherwise it is being called via #setDescription. The difference is that we don't allow |
| * some description fields to change value after project creation. (e.g. project location) |
| */ |
| protected MultiStatus basicSetDescription(ProjectDescription description, int updateFlags) { |
| String message = Messages.resources_projectDesc; |
| MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_WRITE_METADATA, message, null); |
| ProjectDescription current = internalGetDescription(); |
| current.setComment(description.getComment()); |
| current.setSnapshotLocationURI(description.getSnapshotLocationURI()); |
| |
| // set the build order before setting the references or the natures |
| current.setBuildSpec(description.getBuildSpec(true)); |
| |
| // set the references before the natures |
| boolean flushOrder = false; |
| IProject[] oldReferences = current.getReferencedProjects(); |
| IProject[] newReferences = description.getReferencedProjects(); |
| if (!Arrays.equals(oldReferences, newReferences)) { |
| current.setReferencedProjects(newReferences); |
| flushOrder = true; |
| } |
| // Update the dynamic state |
| flushOrder |= current.updateDynamicState(description); |
| |
| if (flushOrder) |
| workspace.flushBuildOrder(); |
| |
| // the natures last as this may cause recursive calls to setDescription. |
| if ((updateFlags & IResource.AVOID_NATURE_CONFIG) == 0) |
| workspace.getNatureManager().configureNatures(this, current, description, result); |
| else |
| current.setNatureIds(description.getNatureIds(false)); |
| return result; |
| } |
| |
| @Override |
| public void build(int trigger, IProgressMonitor monitor) throws CoreException { |
| if (!isAccessible()) |
| return; |
| internalBuild(getActiveBuildConfig(), trigger, null, null, monitor); |
| } |
| |
| @Override |
| public void build(int trigger, String builderName, Map<String, String> args, IProgressMonitor monitor) throws CoreException { |
| Assert.isNotNull(builderName); |
| if (!isAccessible()) |
| return; |
| internalBuild(getActiveBuildConfig(), trigger, builderName, args, monitor); |
| } |
| |
| @Override |
| public void build(IBuildConfiguration config, int trigger, IProgressMonitor monitor) throws CoreException { |
| Assert.isNotNull(config); |
| // If project isn't accessible, or doesn't contain the build configuration, nothing to do. |
| if (!isAccessible() || !hasBuildConfig(config.getName())) |
| return; |
| internalBuild(config, trigger, null, null, monitor); |
| } |
| |
| /** |
| * Checks that this resource is accessible. Typically this means that it |
| * exists. In the case of projects, they must also be open. |
| * If phantom is true, phantom resources are considered. |
| * |
| * @exception CoreException if this resource is not accessible |
| */ |
| @Override |
| public void checkAccessible(int flags) throws CoreException { |
| super.checkAccessible(flags); |
| if (!isOpen(flags)) { |
| String message = NLS.bind(Messages.resources_mustBeOpen, getName()); |
| throw new ResourceException(IResourceStatus.PROJECT_NOT_OPEN, getFullPath(), message, null); |
| } |
| } |
| |
| /** |
| * Checks validity of the given project description. |
| */ |
| protected void checkDescription(IProject project, IProjectDescription desc, boolean moving) throws CoreException { |
| URI location = desc.getLocationURI(); |
| String message = Messages.resources_invalidProjDesc; |
| MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INVALID_VALUE, message, null); |
| status.merge(workspace.validateName(desc.getName(), IResource.PROJECT)); |
| if (moving) { |
| // if we got here from a move call then we should check the location in the description since |
| // its possible that we want to do a rename without moving the contents. (and we shouldn't |
| // throw an Overlapping mapping exception in this case) So if the source description's location |
| // is null (we are using the default) or if the locations aren't equal, then validate the location |
| // of the new description. Otherwise both locations aren't null and they are equal so ignore validation. |
| URI sourceLocation = internalGetDescription().getLocationURI(); |
| if (sourceLocation == null || !sourceLocation.equals(location)) |
| status.merge(workspace.validateProjectLocationURI(project, location)); |
| } else |
| // otherwise continue on like before |
| status.merge(workspace.validateProjectLocationURI(project, location)); |
| if (!status.isOK()) |
| throw new ResourceException(status); |
| } |
| |
| @Override |
| public void close(IProgressMonitor monitor) throws CoreException { |
| String msg = NLS.bind(Messages.resources_closing_1, getName()); |
| SubMonitor subMonitor = SubMonitor.convert(monitor, msg, 100); |
| final ISchedulingRule rule = workspace.getRuleFactory().modifyRule(this); |
| try { |
| workspace.prepareOperation(rule, subMonitor.newChild(1)); |
| ResourceInfo info = getResourceInfo(false, false); |
| int flags = getFlags(info); |
| checkExists(flags, true); |
| subMonitor.subTask(msg); |
| if (!isOpen(flags)) |
| return; |
| // Signal that this resource is about to be closed. Do this at the very |
| // beginning so that infrastructure pieces have a chance to do clean up |
| // while the resources still exist. |
| workspace.beginOperation(true); |
| workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_CLOSE, this)); |
| // flush the build order early in case there is a problem |
| workspace.flushBuildOrder(); |
| IProgressMonitor sub = subMonitor.newChild(49, SubMonitor.SUPPRESS_SUBTASK); |
| IStatus saveStatus = workspace.getSaveManager().save(ISaveContext.PROJECT_SAVE, this, sub); |
| internalClose(subMonitor.newChild(49)); |
| if (saveStatus != null && !saveStatus.isOK()) |
| throw new ResourceException(saveStatus); |
| } catch (OperationCanceledException e) { |
| workspace.getWorkManager().operationCanceled(); |
| throw e; |
| } finally { |
| workspace.endOperation(rule, true); |
| } |
| } |
| |
| @Override |
| public void copy(IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException { |
| // FIXME - the logic here for copying projects needs to be moved to Resource.copy |
| // so that IResource.copy(IPath,int,IProgressMonitor) works properly for |
| // projects and honours all update flags |
| monitor = Policy.monitorFor(monitor); |
| if (destination.segmentCount() == 1) { |
| // copy project to project |
| String projectName = destination.segment(0); |
| IProjectDescription desc = getDescription(); |
| desc.setName(projectName); |
| desc.setLocation(null); |
| ((ProjectDescription) desc).setSnapshotLocationURI(null); |
| internalCopy(desc, updateFlags, monitor); |
| } else { |
| // will fail since we're trying to copy a project to a non-project |
| checkCopyRequirements(destination, IResource.PROJECT, updateFlags); |
| } |
| } |
| |
| @Override |
| public void copy(IProjectDescription destination, int updateFlags, IProgressMonitor monitor) throws CoreException { |
| // FIXME - the logic here for copying projects needs to be moved to Resource.copy |
| // so that IResource.copy(IProjectDescription,int,IProgressMonitor) works properly for |
| // projects and honours all update flags |
| Assert.isNotNull(destination); |
| internalCopy(destination, updateFlags, monitor); |
| } |
| |
| protected void copyMetaArea(IProject source, IProject destination, IProgressMonitor monitor) throws CoreException { |
| IFileStore oldMetaArea = EFS.getFileSystem(EFS.SCHEME_FILE).getStore(workspace.getMetaArea().locationFor(source)); |
| IFileStore newMetaArea = EFS.getFileSystem(EFS.SCHEME_FILE).getStore(workspace.getMetaArea().locationFor(destination)); |
| oldMetaArea.copy(newMetaArea, EFS.NONE, monitor); |
| } |
| |
| @Override |
| public void create(IProgressMonitor monitor) throws CoreException { |
| create(null, monitor); |
| } |
| |
| @Override |
| public void create(IProjectDescription description, IProgressMonitor monitor) throws CoreException { |
| create(description, IResource.NONE, monitor); |
| } |
| |
| @Override |
| public void create(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException { |
| monitor = Policy.monitorFor(monitor); |
| try { |
| monitor.beginTask(Messages.resources_create, Policy.totalWork); |
| checkValidPath(path, PROJECT, false); |
| final ISchedulingRule rule = workspace.getRuleFactory().createRule(this); |
| try { |
| workspace.prepareOperation(rule, monitor); |
| if (description == null) { |
| description = new ProjectDescription(); |
| description.setName(getName()); |
| } |
| assertCreateRequirements(description); |
| workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_CREATE, this)); |
| workspace.beginOperation(true); |
| workspace.createResource(this, updateFlags); |
| workspace.getMetaArea().create(this); |
| ProjectInfo info = (ProjectInfo) getResourceInfo(false, true); |
| |
| // setup description to obtain project location |
| ProjectDescription desc = (ProjectDescription) ((ProjectDescription) description).clone(); |
| desc.setLocationURI(FileUtil.canonicalURI(description.getLocationURI())); |
| desc.setName(getName()); |
| internalSetDescription(desc, false); |
| // see if there potentially are already contents on disk |
| final boolean hasSavedDescription = getLocalManager().hasSavedDescription(this); |
| boolean hasContent = hasSavedDescription; |
| //if there is no project description, there might still be content on disk |
| if (!hasSavedDescription) |
| hasContent = getLocalManager().hasSavedContent(this); |
| try { |
| // look for a description on disk |
| if (hasSavedDescription) { |
| updateDescription(); |
| //make sure the .location file is written |
| workspace.getMetaArea().writePrivateDescription(this); |
| } else { |
| //write out the project |
| writeDescription(IResource.FORCE); |
| } |
| } catch (CoreException e) { |
| workspace.deleteResource(this); |
| throw e; |
| } |
| // inaccessible projects have a null modification stamp. |
| // set this after setting the description as #setDescription |
| // updates the stamp |
| info.clearModificationStamp(); |
| //if a project already had content on disk, mark the project as having unknown children |
| if (hasContent) |
| info.set(ICoreConstants.M_CHILDREN_UNKNOWN); |
| workspace.getSaveManager().requestSnapshot(); |
| } catch (OperationCanceledException e) { |
| workspace.getWorkManager().operationCanceled(); |
| throw e; |
| } finally { |
| workspace.endOperation(rule, true); |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| @Override |
| public void delete(boolean deleteContent, boolean force, IProgressMonitor monitor) throws CoreException { |
| int updateFlags = force ? IResource.FORCE : IResource.NONE; |
| updateFlags |= deleteContent ? IResource.ALWAYS_DELETE_PROJECT_CONTENT : IResource.NEVER_DELETE_PROJECT_CONTENT; |
| delete(updateFlags, monitor); |
| } |
| |
| @Override |
| public void delete(boolean force, IProgressMonitor monitor) throws CoreException { |
| int updateFlags = force ? IResource.FORCE : IResource.NONE; |
| delete(updateFlags, monitor); |
| } |
| |
| @Override |
| public void deleteResource(boolean convertToPhantom, MultiStatus status) throws CoreException { |
| super.deleteResource(convertToPhantom, status); |
| // Clear the history store. |
| clearHistory(null); |
| // Delete the project metadata. |
| workspace.getMetaArea().delete(this); |
| } |
| |
| @Override |
| protected void fixupAfterMoveSource() throws CoreException { |
| workspace.deleteResource(this); |
| // check if we deleted a preferences file |
| ProjectPreferences.deleted(this); |
| } |
| |
| @Override |
| public IBuildConfiguration getActiveBuildConfig() throws CoreException { |
| ResourceInfo info = getResourceInfo(false, false); |
| int flags = getFlags(info); |
| checkAccessible(flags); |
| return internalGetActiveBuildConfig(); |
| } |
| |
| @Override |
| public IBuildConfiguration getBuildConfig(String configName) throws CoreException { |
| if (configName == null) |
| return getActiveBuildConfig(); |
| ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); |
| checkAccessible(getFlags(info)); |
| IBuildConfiguration[] configs = internalGetBuildConfigs(false); |
| for (IBuildConfiguration config : configs) { |
| if (config.getName().equals(configName)) { |
| return config; |
| } |
| } |
| throw new ResourceException(IResourceStatus.BUILD_CONFIGURATION_NOT_FOUND, getFullPath(), null, null); |
| } |
| |
| @Override |
| public IBuildConfiguration[] getBuildConfigs() throws CoreException { |
| ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); |
| checkAccessible(getFlags(info)); |
| return internalGetBuildConfigs(true); |
| } |
| |
| @Override |
| public IContentTypeMatcher getContentTypeMatcher() throws CoreException { |
| return workspace.getContentDescriptionManager().getContentTypeMatcher(this); |
| } |
| |
| @Override |
| public String getDefaultCharset(boolean checkImplicit) { |
| // non-existing resources default to parent's charset |
| if (!exists()) |
| return checkImplicit ? ResourcesPlugin.getEncoding() : null; |
| return workspace.getCharsetManager().getCharsetFor(getFullPath(), checkImplicit); |
| } |
| |
| @Override |
| public IProjectDescription getDescription() throws CoreException { |
| ResourceInfo info = getResourceInfo(false, false); |
| checkAccessible(getFlags(info)); |
| ProjectDescription description = ((ProjectInfo) info).getDescription(); |
| //if the project is currently in the middle of being created, the description might not be available yet |
| if (description == null) |
| checkAccessible(NULL_FLAG); |
| return (IProjectDescription) description.clone(); |
| } |
| |
| @Override |
| public IProjectNature getNature(String natureID) throws CoreException { |
| // Has it already been initialized? |
| ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); |
| checkAccessible(getFlags(info)); |
| IProjectNature nature = info.getNature(natureID); |
| if (nature == null) { |
| // Not initialized yet. Does this project have the nature? |
| if (!hasNature(natureID)) |
| return null; |
| nature = workspace.getNatureManager().createNature(this, natureID); |
| info.setNature(natureID, nature); |
| } |
| return nature; |
| } |
| |
| @Override |
| public IContainer getParent() { |
| return workspace.getRoot(); |
| } |
| |
| @Override |
| @Deprecated |
| public IPath getPluginWorkingLocation(IPluginDescriptor plugin) { |
| if (plugin == null) |
| return null; |
| return getWorkingLocation(plugin.getUniqueIdentifier()); |
| } |
| |
| @Override |
| public IProject getProject() { |
| return this; |
| } |
| |
| @Override |
| public IPath getProjectRelativePath() { |
| return Path.EMPTY; |
| } |
| |
| @Override |
| public IPath getRawLocation() { |
| ProjectDescription description = internalGetDescription(); |
| return description == null ? null : description.getLocation(); |
| } |
| |
| @Override |
| public URI getRawLocationURI() { |
| ProjectDescription description = internalGetDescription(); |
| return description == null ? null : description.getLocationURI(); |
| } |
| |
| @Override |
| public IBuildConfiguration[] getReferencedBuildConfigs(String configName, boolean includeMissing) throws CoreException { |
| ResourceInfo info = getResourceInfo(false, false); |
| checkAccessible(getFlags(info)); |
| ProjectDescription description = ((ProjectInfo) info).getDescription(); |
| //if the project is currently in the middle of being created, the description might not be available yet |
| if (description == null) |
| checkAccessible(NULL_FLAG); |
| if (!hasBuildConfig(configName)) |
| throw new ResourceException(IResourceStatus.BUILD_CONFIGURATION_NOT_FOUND, getFullPath(), null, null); |
| return internalGetReferencedBuildConfigs(configName, includeMissing); |
| } |
| |
| @Override |
| public IProject[] getReferencedProjects() throws CoreException { |
| ResourceInfo info = getResourceInfo(false, false); |
| checkAccessible(getFlags(info)); |
| ProjectDescription description = ((ProjectInfo) info).getDescription(); |
| //if the project is currently in the middle of being created, the description might not be available yet |
| if (description == null) |
| checkAccessible(NULL_FLAG); |
| return description.getAllReferences(true); |
| } |
| |
| @Override |
| public void clearCachedDynamicReferences() { |
| ResourceInfo info = getResourceInfo(false, false); |
| if (info == null) { |
| // If the project is not open there is no cached state and so there is nothing to do. |
| return; |
| } |
| ProjectDescription description = ((ProjectInfo) info).getDescription(); |
| if (description == null) { |
| // If the project is in the process of being created there is no cached state and nothing to do. |
| return; |
| } |
| description.clearCachedDynamicReferences(null); |
| } |
| |
| @Override |
| public IProject[] getReferencingProjects() { |
| IProject[] projects = workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN); |
| List<IProject> result = new ArrayList<>(projects.length); |
| for (IProject p : projects) { |
| Project project = (Project) p; |
| if (!project.isAccessible()) |
| continue; |
| ProjectDescription description = project.internalGetDescription(); |
| if (description == null) |
| continue; |
| IProject[] references = description.getAllReferences(false); |
| for (IProject reference : references) |
| if (reference.equals(this)) { |
| result.add(project); |
| break; |
| } |
| } |
| return result.toArray(new IProject[result.size()]); |
| } |
| |
| @Override |
| public int getType() { |
| return PROJECT; |
| } |
| |
| @Override |
| public IPath getWorkingLocation(String id) { |
| if (id == null || !exists()) |
| return null; |
| IPath result = workspace.getMetaArea().getWorkingLocation(this, id); |
| result.toFile().mkdirs(); |
| return result; |
| } |
| |
| @Override |
| public boolean hasBuildConfig(String configName) throws CoreException { |
| ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); |
| checkAccessible(getFlags(info)); |
| return internalHasBuildConfig(configName); |
| } |
| |
| @Override |
| public boolean hasNature(String natureID) throws CoreException { |
| checkAccessible(getFlags(getResourceInfo(false, false))); |
| // use #internal method to avoid copy but still throw an |
| // exception if the resource doesn't exist. |
| IProjectDescription desc = internalGetDescription(); |
| if (desc == null) |
| checkAccessible(NULL_FLAG); |
| return desc.hasNature(natureID); |
| } |
| |
| /** |
| * Implements all build methods on IProject. |
| */ |
| protected void internalBuild(final IBuildConfiguration config, final int trigger, final String builderName, final Map<String, String> args, IProgressMonitor monitor) throws CoreException { |
| workspace.run(new ICoreRunnable() { |
| @Override |
| public void run(IProgressMonitor innerMonitor) throws CoreException { |
| innerMonitor = Policy.monitorFor(innerMonitor); |
| final ISchedulingRule projectBuildRule = workspace.getBuildManager().getRule(config, trigger, builderName, args); |
| final boolean relaxed = Job.getJobManager().currentRule() == null && workspace.isRelaxedRule(projectBuildRule); |
| |
| // PRE + POST_BUILD, and the build itself are allowed to modify resources, so require the current thread's scheduling rule |
| // to either contain the WR or be null. Therefore, if not null, ensure it contains the WR rule... |
| final ISchedulingRule notificationsRule = relaxed ? null : workspace.getRoot(); |
| try { |
| innerMonitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ |
| try { |
| workspace.prepareOperation(notificationsRule, innerMonitor); |
| if (!shouldBuild()) |
| return; |
| workspace.beginOperation(true); |
| workspace.aboutToBuild(Project.this, trigger); |
| } finally { |
| workspace.endOperation(notificationsRule, false); |
| } |
| try { |
| IStatus result; |
| workspace.prepareOperation(projectBuildRule, innerMonitor); |
| //don't open the tree eagerly because it will be wasted if no build occurs |
| workspace.beginOperation(false); |
| result = workspace.getBuildManager().build(config, trigger, builderName, args, Policy.subMonitorFor(innerMonitor, Policy.opWork)); |
| if (!result.isOK()) |
| throw new ResourceException(result); |
| } finally { |
| workspace.endOperation(projectBuildRule, false); |
| try { |
| workspace.prepareOperation(notificationsRule, innerMonitor); |
| //don't open the tree eagerly because it will be wasted if no change occurs |
| workspace.beginOperation(false); |
| workspace.broadcastBuildEvent(Project.this, IResourceChangeEvent.POST_BUILD, trigger); |
| //building may close the tree, so open it |
| if (workspace.getElementTree().isImmutable()) |
| workspace.newWorkingTree(); |
| } finally { |
| workspace.endOperation(notificationsRule, false); |
| } |
| } |
| } finally { |
| innerMonitor.done(); |
| } |
| } |
| |
| /** |
| * Returns whether this project should be built for a given trigger. |
| * @return <code>true</code> if the build should proceed, and <code>false</code> otherwise. |
| */ |
| private boolean shouldBuild() { |
| ResourceInfo info = getResourceInfo(false, false); |
| int flags = getFlags(info); |
| if (!exists(flags, true) || !isOpen(flags)) |
| return false; |
| return true; |
| } |
| |
| }, null, IWorkspace.AVOID_UPDATE, monitor); |
| } |
| |
| /** |
| * Closes the project. This is called during restore when there is a failure |
| * to read the project description. Since it is called during workspace restore, |
| * it cannot start any operations. |
| */ |
| protected void internalClose(IProgressMonitor monitor) throws CoreException { |
| SubMonitor subMonitor = SubMonitor.convert(monitor, 2); |
| workspace.flushBuildOrder(); |
| getMarkerManager().removeMarkers(this, IResource.DEPTH_INFINITE); |
| subMonitor.worked(1); |
| // remove each member from the resource tree. |
| // DO NOT use resource.delete() as this will delete it from disk as well. |
| IResource[] members = members(IContainer.INCLUDE_PHANTOMS | IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); |
| subMonitor.setWorkRemaining(members.length); |
| for (IResource member2 : members) { |
| Resource member = (Resource) member2; |
| workspace.deleteResource(member); |
| subMonitor.worked(1); |
| } |
| // finally mark the project as closed. |
| ResourceInfo info = getResourceInfo(false, true); |
| info.clear(M_OPEN); |
| info.clearSessionProperties(); |
| info.clearModificationStamp(); |
| info.setSyncInfo(null); |
| } |
| |
| protected void internalCopy(IProjectDescription destDesc, int updateFlags, IProgressMonitor monitor) throws CoreException { |
| monitor = Policy.monitorFor(monitor); |
| try { |
| String message = NLS.bind(Messages.resources_copying, getFullPath()); |
| monitor.beginTask(message, Policy.totalWork); |
| String destName = destDesc.getName(); |
| IPath destPath = new Path(destName).makeAbsolute(); |
| Project destination = (Project) workspace.getRoot().getProject(destName); |
| final ISchedulingRule rule = workspace.getRuleFactory().copyRule(this, destination); |
| try { |
| workspace.prepareOperation(rule, monitor); |
| // The following assert method throws CoreExceptions as stated in the IProject.copy API |
| // and assert for programming errors. See checkCopyRequirements for more information. |
| assertCopyRequirements(destPath, IResource.PROJECT, updateFlags); |
| checkDescription(destination, destDesc, false); |
| workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_COPY, this, destination, updateFlags)); |
| |
| workspace.beginOperation(true); |
| getLocalManager().refresh(this, DEPTH_INFINITE, true, Policy.subMonitorFor(monitor, Policy.opWork * 20 / 100)); |
| |
| // close the property store so incorrect info is not copied to the destination |
| getPropertyManager().closePropertyStore(this); |
| getLocalManager().getHistoryStore().closeHistoryStore(this); |
| |
| // copy the meta area for the project |
| copyMetaArea(this, destination, Policy.subMonitorFor(monitor, Policy.opWork * 5 / 100)); |
| |
| // copy just the project and not its children yet (tree node, properties) |
| internalCopyProjectOnly(destination, destDesc, Policy.subMonitorFor(monitor, Policy.opWork * 5 / 100)); |
| |
| // set the description |
| destination.internalSetDescription(destDesc, false); |
| |
| //create the directory for the new project |
| destination.getStore().mkdir(EFS.NONE, Policy.subMonitorFor(monitor, Policy.opWork * 5 / 100)); |
| |
| // call super.copy for each child (excluding project description file) |
| //make it a best effort copy |
| message = Messages.resources_copyProblem; |
| MultiStatus problems = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null); |
| |
| IResource[] children = members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); |
| final int childCount = children.length; |
| final int childWork = childCount > 1 ? Policy.opWork * 50 / 100 / (childCount - 1) : 0; |
| for (int i = 0; i < childCount; i++) { |
| IResource child = children[i]; |
| if (!isProjectDescriptionFile(child)) { |
| try { |
| child.copy(destPath.append(child.getName()), updateFlags, Policy.subMonitorFor(monitor, childWork)); |
| } catch (CoreException e) { |
| problems.merge(e.getStatus()); |
| } |
| } |
| } |
| |
| // write out the new project description to the meta area |
| try { |
| destination.writeDescription(IResource.FORCE); |
| } catch (CoreException e) { |
| try { |
| destination.delete((updateFlags & IResource.FORCE) != 0, null); |
| } catch (CoreException e2) { |
| // ignore and rethrow the exception that got us here |
| } |
| throw e; |
| } |
| monitor.worked(Policy.opWork * 5 / 100); |
| |
| // refresh local |
| monitor.subTask(Messages.resources_updating); |
| getLocalManager().refresh(destination, DEPTH_INFINITE, true, Policy.subMonitorFor(monitor, Policy.opWork * 10 / 100)); |
| if (!problems.isOK()) |
| throw new ResourceException(problems); |
| } catch (OperationCanceledException e) { |
| workspace.getWorkManager().operationCanceled(); |
| throw e; |
| } finally { |
| workspace.endOperation(rule, true); |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| /* |
| * Copies just the project and no children. Does NOT copy the meta area. |
| */ |
| protected void internalCopyProjectOnly(IResource destination, IProjectDescription destDesc, IProgressMonitor monitor) throws CoreException { |
| // close the property store so bogus values aren't copied to the destination |
| getPropertyManager().closePropertyStore(this); |
| getLocalManager().getHistoryStore().closeHistoryStore(this); |
| // copy the tree and properties |
| workspace.copyTree(this, destination.getFullPath(), IResource.DEPTH_ZERO, IResource.NONE, false); |
| getPropertyManager().copy(this, destination, IResource.DEPTH_ZERO); |
| |
| ProjectInfo info = (ProjectInfo) ((Resource) destination).getResourceInfo(false, true); |
| |
| //copy the hidden metadata that we store in the project description |
| ProjectDescription projectDesc = (ProjectDescription) destDesc; |
| ProjectDescription internalDesc = ((Project) destination.getProject()).internalGetDescription(); |
| projectDesc.setLinkDescriptions(internalDesc.getLinks()); |
| projectDesc.setFilterDescriptions(internalDesc.getFilters()); |
| projectDesc.setVariableDescriptions(internalDesc.getVariables()); |
| |
| //clear properties, markers, and description for the new project, because they shouldn't be copied. |
| info.description = null; |
| info.natures = null; |
| info.setMarkers(null); |
| info.clearSessionProperties(); |
| } |
| |
| /** |
| * Like {@link #getActiveBuildConfig()} but doesn't check accessibility. |
| * Project must be accessible. |
| * @see #getActiveBuildConfig() |
| */ |
| IBuildConfiguration internalGetActiveBuildConfig() { |
| String configName = internalGetDescription().activeConfiguration; |
| try { |
| if (configName != null) |
| return getBuildConfig(configName); |
| } catch (CoreException e) { |
| // Build configuration doesn't exist; treat the first as active. |
| } |
| return internalGetBuildConfigs(false)[0]; |
| } |
| |
| /** |
| * @return IBuildConfiguration[] always contains at least one build configuration |
| */ |
| public IBuildConfiguration[] internalGetBuildConfigs(boolean makeCopy) { |
| ProjectDescription desc = internalGetDescription(); |
| if (desc == null) |
| return new IBuildConfiguration[] {new BuildConfiguration(this, IBuildConfiguration.DEFAULT_CONFIG_NAME)}; |
| return desc.getBuildConfigs(this, makeCopy); |
| } |
| |
| /** |
| * This is an internal helper method. This implementation is different from the API |
| * method getDescription(). This one does not check the project accessibility. It exists |
| * in order to prevent "chicken and egg" problems in places like the project creation. |
| * It may return null. |
| */ |
| public ProjectDescription internalGetDescription() { |
| ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); |
| if (info == null) |
| return null; |
| return info.getDescription(); |
| } |
| |
| /** |
| * Returns the IBuildConfigurations referenced by the passed in build configuration |
| * @param configName to find references for |
| * @return IBuildConfiguration[] of referenced configurations; never null. |
| */ |
| public IBuildConfiguration[] internalGetReferencedBuildConfigs(String configName, boolean includeMissing) { |
| ProjectDescription description = internalGetDescription(); |
| IBuildConfiguration[] refs = description.getAllBuildConfigReferences(configName, false); |
| Collection<IBuildConfiguration> configs = new LinkedHashSet<>(refs.length); |
| for (IBuildConfiguration ref : refs) { |
| try { |
| configs.add((((BuildConfiguration) ref).getBuildConfig())); |
| } catch (CoreException e) { |
| // The project isn't accessible, or the build configuration doesn't exist |
| // on the project. If requested return the full set of build references which may |
| // be useful to API consumers |
| if (includeMissing) |
| configs.add(ref); |
| } |
| } |
| return configs.toArray(new IBuildConfiguration[configs.size()]); |
| } |
| |
| boolean internalHasBuildConfig(String configName) { |
| return internalGetDescription().hasBuildConfig(configName); |
| } |
| |
| /** |
| * Sets this project's description to the given value. This is the body of the |
| * corresponding API method but is needed separately since it is used |
| * during workspace restore (i.e., when you cannot do an operation) |
| */ |
| void internalSetDescription(IProjectDescription value, boolean incrementContentId) { |
| // Project has been added / removed. Build order is out-of-step |
| workspace.flushBuildOrder(); |
| |
| ProjectInfo info = (ProjectInfo) getResourceInfo(false, true); |
| info.setDescription((ProjectDescription) value); |
| getLocalManager().setLocation(this, info, value.getLocationURI()); |
| if (incrementContentId) { |
| info.incrementContentId(); |
| //if the project is not accessible, stamp will be null and should remain null |
| if (info.getModificationStamp() != NULL_STAMP) |
| workspace.updateModificationStamp(info); |
| } |
| } |
| |
| @Override |
| public void internalSetLocal(boolean flag, int depth) throws CoreException { |
| // do nothing for projects, but call for its children |
| if (depth == IResource.DEPTH_ZERO) |
| return; |
| if (depth == IResource.DEPTH_ONE) |
| depth = IResource.DEPTH_ZERO; |
| // get the children via the workspace since we know that this |
| // resource exists (it is local). |
| IResource[] children = getChildren(IResource.NONE); |
| for (IResource element : children) |
| ((Resource) element).internalSetLocal(flag, depth); |
| } |
| |
| @Override |
| public boolean isAccessible() { |
| return isOpen(); |
| } |
| |
| @Override |
| public boolean isDerived(int options) { |
| //projects are never derived |
| return false; |
| } |
| |
| @Override |
| public boolean isLinked(int options) { |
| return false;//projects are never linked |
| } |
| |
| @Override |
| public boolean isVirtual() { |
| return false; // projects are never virtual |
| } |
| |
| @Override |
| public boolean isTeamPrivateMember(int options) { |
| return false;//projects are never team private members |
| } |
| |
| @Deprecated |
| @Override |
| public boolean isLocal(int depth) { |
| // the flags parameter is ignored for projects so pass anything |
| return isLocal(-1, depth); |
| } |
| |
| @Deprecated |
| @Override |
| public boolean isLocal(int flags, int depth) { |
| // don't check the flags....projects are always local |
| if (depth == DEPTH_ZERO) |
| return true; |
| if (depth == DEPTH_ONE) |
| depth = DEPTH_ZERO; |
| // get the children via the workspace since we know that this |
| // resource exists (it is local). |
| IResource[] children = getChildren(IResource.NONE); |
| for (int i = 0; i < children.length; i++) |
| if (!children[i].isLocal(depth)) |
| return false; |
| return true; |
| } |
| |
| @Override |
| public boolean isNatureEnabled(String natureId) throws CoreException { |
| checkAccessible(getFlags(getResourceInfo(false, false))); |
| return workspace.getNatureManager().isNatureEnabled(this, natureId); |
| } |
| |
| @Override |
| public boolean isOpen() { |
| ResourceInfo info = getResourceInfo(false, false); |
| return isOpen(getFlags(info)); |
| } |
| |
| public boolean isOpen(int flags) { |
| return flags != NULL_FLAG && ResourceInfo.isSet(flags, M_OPEN); |
| } |
| |
| /** |
| * Returns true if this resource represents the project description file, and |
| * false otherwise. |
| */ |
| protected boolean isProjectDescriptionFile(IResource resource) { |
| return resource.getType() == IResource.FILE && resource.getFullPath().segmentCount() == 2 && resource.getName().equals(IProjectDescription.DESCRIPTION_FILE_NAME); |
| } |
| |
| @Override |
| public void loadSnapshot(int options, URI snapshotLocation, IProgressMonitor monitor) throws CoreException { |
| // load a snapshot of refresh information when project is not opened |
| if (isOpen()) { |
| String message = Messages.resources_projectMustNotBeOpen; |
| MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, null); |
| throw new CoreException(status); |
| } |
| internalLoadSnapshot(options, snapshotLocation, monitor); |
| } |
| |
| /** |
| * Loads a snapshot of project meta-data from the given location URI. |
| * Like {@link IProject#loadSnapshot(int, URI, IProgressMonitor)} but can be |
| * called when the project is open. |
| * |
| * @see IProject#saveSnapshot(int, URI, IProgressMonitor) |
| */ |
| private void internalLoadSnapshot(int options, URI snapshotLocation, IProgressMonitor monitor) throws CoreException { |
| if ((options & SNAPSHOT_TREE) != 0) { |
| // ensure that path variables are resolved: only ws accessible while project is closed |
| snapshotLocation = workspace.getPathVariableManager().resolveURI(snapshotLocation); |
| if (!snapshotLocation.isAbsolute()) { |
| String message = NLS.bind(Messages.projRead_badSnapshotLocation, snapshotLocation.toString()); |
| throw new CoreException(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, message, null)); |
| } |
| // copy the snapshot from the URI into the project metadata |
| IPath snapshotPath = workspace.getMetaArea().getRefreshLocationFor(this); |
| IFileStore snapshotFileStore = EFS.getStore(org.eclipse.core.filesystem.URIUtil.toURI(snapshotPath)); |
| EFS.getStore(snapshotLocation).copy(snapshotFileStore, EFS.OVERWRITE, monitor); |
| } |
| } |
| |
| @Override |
| public void move(IProjectDescription destination, boolean force, IProgressMonitor monitor) throws CoreException { |
| Assert.isNotNull(destination); |
| move(destination, force ? IResource.FORCE : IResource.NONE, monitor); |
| } |
| |
| @Override |
| public void move(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException { |
| Assert.isNotNull(description); |
| monitor = Policy.monitorFor(monitor); |
| try { |
| String message = NLS.bind(Messages.resources_moving, getFullPath()); |
| monitor.beginTask(message, Policy.totalWork); |
| IProject destination = workspace.getRoot().getProject(description.getName()); |
| final ISchedulingRule rule = workspace.getRuleFactory().moveRule(this, destination); |
| try { |
| workspace.prepareOperation(rule, monitor); |
| // The following assert method throws CoreExceptions as stated in the IResource.move API |
| // and assert for programming errors. See checkMoveRequirements for more information. |
| if (!getName().equals(description.getName())) { |
| IPath destPath = Path.ROOT.append(description.getName()); |
| assertMoveRequirements(destPath, IResource.PROJECT, updateFlags); |
| } |
| checkDescription(destination, description, true); |
| workspace.beginOperation(true); |
| message = Messages.resources_moveProblem; |
| MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, null); |
| WorkManager workManager = workspace.getWorkManager(); |
| ResourceTree tree = new ResourceTree(getLocalManager(), workManager.getLock(), status, updateFlags); |
| IMoveDeleteHook hook = workspace.getMoveDeleteHook(); |
| workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_MOVE, this, destination, updateFlags)); |
| int depth = 0; |
| try { |
| depth = workManager.beginUnprotected(); |
| if (!hook.moveProject(tree, this, description, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork / 2))) |
| tree.standardMoveProject(this, description, updateFlags, Policy.subMonitorFor(monitor, Policy.opWork / 2)); |
| } finally { |
| workManager.endUnprotected(depth); |
| } |
| // Invalidate the tree for further use by clients. |
| tree.makeInvalid(); |
| if (!tree.getStatus().isOK()) |
| throw new ResourceException(tree.getStatus()); |
| // make sure the move operation is remembered |
| workspace.getSaveManager().requestSnapshot(); |
| } catch (OperationCanceledException e) { |
| workspace.getWorkManager().operationCanceled(); |
| throw e; |
| } finally { |
| workspace.endOperation(rule, true); |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| @Override |
| public void open(int updateFlags, IProgressMonitor monitor) throws CoreException { |
| monitor = Policy.monitorFor(monitor); |
| try { |
| String msg = NLS.bind(Messages.resources_opening_1, getName()); |
| monitor.beginTask(msg, Policy.totalWork); |
| monitor.subTask(msg); |
| final ISchedulingRule rule = workspace.getRuleFactory().modifyRule(this); |
| try { |
| workspace.prepareOperation(rule, monitor); |
| ProjectInfo info = (ProjectInfo) getResourceInfo(false, false); |
| int flags = getFlags(info); |
| checkExists(flags, true); |
| if (isOpen(flags)) |
| return; |
| |
| workspace.beginOperation(true); |
| // flush the build order early in case there is a problem |
| workspace.flushBuildOrder(); |
| info = (ProjectInfo) getResourceInfo(false, true); |
| info.set(M_OPEN); |
| //clear the unknown children immediately to avoid background refresh |
| boolean unknownChildren = info.isSet(M_CHILDREN_UNKNOWN); |
| if (unknownChildren) |
| info.clear(M_CHILDREN_UNKNOWN); |
| // the M_USED flag is used to indicate the difference between opening a project |
| // for the first time and opening it from a previous close (restoring it from disk) |
| boolean used = info.isSet(M_USED); |
| boolean snapshotLoaded = false; |
| if (!used && !workspace.getMetaArea().getRefreshLocationFor(this).toFile().exists()) { |
| //auto-load a refresh snapshot if it is set |
| final boolean hasSavedDescription = getLocalManager().hasSavedDescription(this); |
| if (hasSavedDescription) { |
| ProjectDescription updatedDesc = info.getDescription(); |
| if (updatedDesc != null) { |
| URI autoloadURI = updatedDesc.getSnapshotLocationURI(); |
| if (autoloadURI != null) { |
| try { |
| autoloadURI = getPathVariableManager().resolveURI(autoloadURI); |
| internalLoadSnapshot(SNAPSHOT_TREE, autoloadURI, Policy.subMonitorFor(monitor, Policy.opWork * 5 / 100)); |
| snapshotLoaded = true; |
| } catch (CoreException ce) { |
| //Log non-existing autoload snapshot as warning only |
| String msgerr = NLS.bind(Messages.projRead_cannotReadSnapshot, getName(), ce.getLocalizedMessage()); |
| Policy.log(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, msgerr)); |
| } |
| } |
| } |
| } |
| } |
| boolean minorIssuesDuringRestore = false; |
| if (used) { |
| minorIssuesDuringRestore = workspace.getSaveManager().restore(this, Policy.subMonitorFor(monitor, Policy.opWork * 20 / 100)); |
| } else { |
| info.set(M_USED); |
| //reconcile any links and groups in the project description |
| IStatus result = reconcileLinksAndGroups(info.getDescription()); |
| if (!result.isOK()) |
| throw new CoreException(result); |
| workspace.updateModificationStamp(info); |
| monitor.worked(Policy.opWork * (snapshotLoaded ? 15 : 20) / 100); |
| } |
| startup(); |
| //request a refresh if the project is new and has unknown members on disk |
| // or restore of the project is not fully successful |
| if ((!used && unknownChildren) || !minorIssuesDuringRestore) { |
| boolean refreshed = false; |
| if (!used) { |
| refreshed = workspace.getSaveManager().restoreFromRefreshSnapshot(this, Policy.subMonitorFor(monitor, Policy.opWork * 20 / 100)); |
| if (refreshed) { // account for the refresh work |
| monitor.worked(Policy.opWork * 60 / 100); |
| } |
| } |
| //refresh either in background or foreground |
| if (!refreshed) { |
| if ((updateFlags & IResource.BACKGROUND_REFRESH) != 0) { |
| workspace.refreshManager.refresh(this); |
| monitor.worked(Policy.opWork * 60 / 100); |
| } else { |
| refreshLocal(IResource.DEPTH_INFINITE, Policy.subMonitorFor(monitor, Policy.opWork * 60 / 100)); |
| } |
| } |
| } |
| //creation of this project may affect overlapping resources |
| workspace.getAliasManager().updateAliases(this, getStore(), IResource.DEPTH_INFINITE, monitor); |
| } catch (OperationCanceledException e) { |
| workspace.getWorkManager().operationCanceled(); |
| throw e; |
| } finally { |
| workspace.endOperation(rule, true); |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| @Override |
| public void open(IProgressMonitor monitor) throws CoreException { |
| open(IResource.NONE, monitor); |
| } |
| |
| /** |
| * The project description file has changed on disk, resulting in a changed |
| * set of linked resources. Perform the necessary creations and deletions of |
| * links to bring the links in sync with those described in the project description. |
| * @param newDescription the new project description that may have |
| * changed link descriptions. |
| * @return status ok if everything went well, otherwise an ERROR multi-status |
| * describing the problems encountered. |
| */ |
| public IStatus reconcileLinksAndGroups(ProjectDescription newDescription) { |
| String msg = Messages.links_errorLinkReconcile; |
| HashMap<IPath, LinkDescription> newLinks = newDescription.getLinks(); |
| MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.OPERATION_FAILED, msg, null); |
| //walk over old linked resources and remove those that are no longer defined |
| ProjectDescription oldDescription = internalGetDescription(); |
| if (oldDescription != null) { |
| HashMap<IPath, LinkDescription> oldLinks = oldDescription.getLinks(); |
| if (oldLinks != null) { |
| for (LinkDescription oldLink : oldLinks.values()) { |
| Resource oldLinkResource = (Resource) findMember(oldLink.getProjectRelativePath()); |
| if (oldLinkResource == null || !oldLinkResource.isLinked()) |
| continue; |
| LinkDescription newLink = null; |
| if (newLinks != null) |
| newLink = newLinks.get(oldLink.getProjectRelativePath()); |
| //if the new link is missing, or has different (raw) location or gender, then remove old link |
| if (newLink == null || !newLink.getLocationURI().equals(oldLinkResource.getRawLocationURI()) || newLink.getType() != oldLinkResource.getType()) { |
| try { |
| oldLinkResource.delete(IResource.NONE, null); |
| //refresh the resource, because removing a link can reveal a previously hidden resource in parent |
| oldLinkResource.refreshLocal(IResource.DEPTH_INFINITE, null); |
| } catch (CoreException e) { |
| status.merge(e.getStatus()); |
| } |
| } |
| } |
| } |
| } |
| // walk over new links and groups and create if necessary |
| // Recreate them in order of the higher up in the tree hierarchy first, |
| // so we don't have to create intermediate directories that would turn |
| // out |
| // to be groups or link folders. |
| if (newLinks == null) |
| return status; |
| //sort links to avoid creating nested links before their parents |
| TreeSet<LinkDescription> newLinksAndGroups = new TreeSet<>((arg0, arg1) -> { |
| int numberOfSegments0 = arg0.getProjectRelativePath().segmentCount(); |
| int numberOfSegments1 = arg1.getProjectRelativePath().segmentCount(); |
| if (numberOfSegments0 != numberOfSegments1) |
| return numberOfSegments0 - numberOfSegments1; |
| else if (arg0.equals(arg1)) |
| return 0; |
| |
| return -1; |
| }); |
| if (newLinks != null) |
| newLinksAndGroups.addAll(newLinks.values()); |
| |
| for (LinkDescription newLink : newLinksAndGroups) { |
| try { |
| Resource toLink = workspace.newResource(getFullPath().append(newLink.getProjectRelativePath()), newLink.getType()); |
| IContainer parent = toLink.getParent(); |
| if (parent != null && !parent.exists() && parent.getType() == FOLDER) |
| ((Folder) parent).ensureExists(Policy.monitorFor(null)); |
| if (!toLink.exists() || !toLink.isLinked()) { |
| if (newLink.isGroup()) |
| ((Folder) toLink).create(IResource.REPLACE | IResource.VIRTUAL, true, null); |
| else |
| toLink.createLink(newLink.getLocationURI(), IResource.REPLACE | IResource.ALLOW_MISSING_LOCAL, null); |
| } |
| } catch (CoreException e) { |
| status.merge(e.getStatus()); |
| } |
| } |
| return status; |
| } |
| |
| @Override |
| public void saveSnapshot(int options, URI snapshotLocation, IProgressMonitor monitor) throws CoreException { |
| monitor = Policy.monitorFor(monitor); |
| try { |
| monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$ |
| //Project must be open such that variables can be resolved |
| checkAccessible(getFlags(getResourceInfo(false, false))); |
| //URI must not be null and must not refer to undefined path variables |
| URI resolvedSnapshotLocation = getPathVariableManager().resolveURI(snapshotLocation); |
| if (resolvedSnapshotLocation == null || !resolvedSnapshotLocation.isAbsolute()) { |
| String message = NLS.bind(Messages.projRead_badSnapshotLocation, resolvedSnapshotLocation); |
| throw new CoreException(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, message, null)); |
| } |
| if ((options & SNAPSHOT_TREE) != 0) { |
| // write a snapshot of refresh information |
| try { |
| IProgressMonitor sub = Policy.subMonitorFor(monitor, Policy.opWork / 2); |
| workspace.getSaveManager().saveRefreshSnapshot(this, resolvedSnapshotLocation, sub); |
| } catch (OperationCanceledException e) { |
| //workspace.getWorkManager().operationCanceled(); |
| throw e; |
| } |
| } |
| if ((options & SNAPSHOT_SET_AUTOLOAD) != 0) { |
| IProgressMonitor sub = Policy.subMonitorFor(monitor, Policy.opWork / 2); |
| //Make absolute URI inside the project relative |
| if (snapshotLocation != null && snapshotLocation.isAbsolute()) { |
| snapshotLocation = getPathVariableManager().convertToRelative(snapshotLocation, false, "PROJECT_LOC"); //$NON-NLS-1$ |
| } |
| IProjectDescription desc = getDescription(); |
| ((ProjectDescription) desc).setSnapshotLocationURI(snapshotLocation); |
| setDescription(desc, IResource.KEEP_HISTORY | IResource.AVOID_NATURE_CONFIG, sub); |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| @Override |
| public void setDescription(IProjectDescription description, int updateFlags, IProgressMonitor monitor) throws CoreException { |
| // FIXME - update flags should be honored: |
| // KEEP_HISTORY means capture .project file in local history |
| // FORCE means overwrite any existing .project file |
| monitor = Policy.monitorFor(monitor); |
| try { |
| monitor.beginTask(Messages.resources_setDesc, Policy.totalWork); |
| ISchedulingRule rule = null; |
| if ((updateFlags & IResource.AVOID_NATURE_CONFIG) != 0) |
| rule = workspace.getRuleFactory().modifyRule(this); |
| else |
| rule = workspace.getRoot(); |
| try { |
| //need to use root rule because nature configuration calls third party code |
| workspace.prepareOperation(rule, monitor); |
| ResourceInfo info = getResourceInfo(false, false); |
| checkAccessible(getFlags(info)); |
| //if nothing has changed, we don't need to do anything |
| ProjectDescription oldDescription = internalGetDescription(); |
| ProjectDescription newDescription = (ProjectDescription) description; |
| boolean hasPublicChanges = oldDescription.hasPublicChanges(newDescription); |
| boolean hasPrivateChanges = oldDescription.hasPrivateChanges(newDescription); |
| if (!hasPublicChanges && !hasPrivateChanges) |
| return; |
| checkDescription(this, newDescription, false); |
| //If we're out of sync and !FORCE, then fail. |
| //If the file is missing, we want to write the new description then throw an exception. |
| boolean hadSavedDescription = true; |
| if (((updateFlags & IResource.FORCE) == 0)) { |
| hadSavedDescription = getLocalManager().hasSavedDescription(this); |
| if (hadSavedDescription && !getLocalManager().isDescriptionSynchronized(this)) { |
| String message = NLS.bind(Messages.resources_projectDescSync, getName()); |
| throw new ResourceException(IResourceStatus.OUT_OF_SYNC_LOCAL, getFullPath(), message, null); |
| } |
| } |
| //see if we have an old .prj file |
| if (!hadSavedDescription) |
| hadSavedDescription = workspace.getMetaArea().hasSavedProject(this); |
| workspace.beginOperation(true); |
| MultiStatus status = basicSetDescription(newDescription, updateFlags); |
| if (hadSavedDescription && !status.isOK()) |
| throw new CoreException(status); |
| //write the new description to the .project file |
| writeDescription(oldDescription, updateFlags, hasPublicChanges, hasPrivateChanges); |
| //increment the content id even for private changes |
| info = getResourceInfo(false, true); |
| info.incrementContentId(); |
| workspace.updateModificationStamp(info); |
| if (!hadSavedDescription) { |
| String msg = NLS.bind(Messages.resources_missingProjectMetaRepaired, getName()); |
| status.merge(new ResourceStatus(IResourceStatus.MISSING_DESCRIPTION_REPAIRED, getFullPath(), msg)); |
| } |
| if (!status.isOK()) |
| throw new CoreException(status); |
| } finally { |
| workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.POST_PROJECT_CHANGE, this)); |
| workspace.endOperation(rule, true); |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| @Override |
| public void setDescription(IProjectDescription description, IProgressMonitor monitor) throws CoreException { |
| // funnel all operations to central method |
| setDescription(description, IResource.KEEP_HISTORY, monitor); |
| } |
| |
| /** |
| * Restore the non-persisted state for the project. For example, read and set |
| * the description from the local meta area. Also, open the property store etc. |
| * This method is used when an open project is restored and so emulates |
| * the behaviour of open(). |
| */ |
| protected void startup() throws CoreException { |
| if (!isOpen()) |
| return; |
| workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_OPEN, this)); |
| } |
| |
| @Override |
| public void touch(IProgressMonitor monitor) throws CoreException { |
| monitor = Policy.monitorFor(monitor); |
| try { |
| String message = NLS.bind(Messages.resources_touch, getFullPath()); |
| monitor.beginTask(message, Policy.totalWork); |
| final ISchedulingRule rule = workspace.getRuleFactory().modifyRule(this); |
| try { |
| workspace.prepareOperation(rule, monitor); |
| workspace.beginOperation(true); |
| super.touch(Policy.subMonitorFor(monitor, Policy.opWork)); |
| } catch (OperationCanceledException e) { |
| workspace.getWorkManager().operationCanceled(); |
| throw e; |
| } finally { |
| workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.POST_PROJECT_CHANGE, this)); |
| workspace.endOperation(rule, true); |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| /** |
| * The project description file on disk is better than the description in memory. |
| * Make sure the project description in memory is synchronized with the |
| * description file contents. |
| */ |
| protected void updateDescription() throws CoreException { |
| if (ProjectDescription.isWriting) |
| return; |
| ProjectDescription.isReading = true; |
| try { |
| ProjectDescription description = getLocalManager().read(this, false); |
| //links can only be created if the project is open |
| IStatus result = null; |
| if (isOpen()) |
| result = reconcileLinksAndGroups(description); |
| internalSetDescription(description, true); |
| if (result != null && !result.isOK()) |
| throw new CoreException(result); |
| } finally { |
| workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.POST_PROJECT_CHANGE, this)); |
| ProjectDescription.isReading = false; |
| } |
| } |
| |
| /** |
| * Writes the project's current description file to disk. |
| */ |
| public void writeDescription(int updateFlags) throws CoreException { |
| writeDescription(internalGetDescription(), updateFlags, true, true); |
| } |
| |
| /** |
| * Writes the project description file to disk. This is the only method |
| * that should ever be writing the description, because it ensures that |
| * the description isn't then immediately discovered as an incoming |
| * change and read back from disk. |
| * @param description The description to write |
| * @param updateFlags The write operation update flags |
| * @param hasPublicChanges Whether the public sections of the description have changed |
| * @param hasPrivateChanges Whether the private sections of the description have changed |
| * @exception CoreException On failure to write the description |
| */ |
| public void writeDescription(IProjectDescription description, int updateFlags, boolean hasPublicChanges, boolean hasPrivateChanges) throws CoreException { |
| if (ProjectDescription.isReading) |
| return; |
| ProjectDescription.isWriting = true; |
| try { |
| getLocalManager().internalWrite(this, description, updateFlags, hasPublicChanges, hasPrivateChanges); |
| } finally { |
| ProjectDescription.isWriting = false; |
| } |
| } |
| } |