blob: ef5c9e5f166a5294ce402e1b2f498e8f67afe917 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2015 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.core.internal.resources.mapping;
import org.eclipse.core.internal.utils.Policy;
import org.eclipse.core.resources.*;
import org.eclipse.core.resources.mapping.IResourceChangeDescriptionFactory;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
/**
* Factory for creating a resource delta that describes a proposed change.
*/
public class ResourceChangeDescriptionFactory implements IResourceChangeDescriptionFactory {
private ProposedResourceDelta root = new ProposedResourceDelta(ResourcesPlugin.getWorkspace().getRoot());
/**
* Creates and a delta representing a deleted resource, and adds it to the provided
* parent delta.
* @param parentDelta The parent of the deletion delta to create
* @param resource The deleted resource to create a delta for
*/
private ProposedResourceDelta buildDeleteDelta(ProposedResourceDelta parentDelta, IResource resource) {
//start with the existing delta for this resource, if any, to preserve other flags
ProposedResourceDelta delta = parentDelta.getChild(resource.getName());
if (delta == null) {
delta = new ProposedResourceDelta(resource);
parentDelta.add(delta);
}
delta.setKind(IResourceDelta.REMOVED);
if (resource.getType() == IResource.FILE)
return delta;
//recurse to build deletion deltas for children
try {
IResource[] members = ((IContainer) resource).members();
int childCount = members.length;
if (childCount > 0) {
ProposedResourceDelta[] childDeltas = new ProposedResourceDelta[childCount];
for (int i = 0; i < childCount; i++)
childDeltas[i] = buildDeleteDelta(delta, members[i]);
}
} catch (CoreException e) {
//don't need to create deletion deltas for children of inaccessible resources
}
return delta;
}
@Override
public void change(IFile file) {
ProposedResourceDelta delta = getDelta(file);
if (delta.getKind() == 0)
delta.setKind(IResourceDelta.CHANGED);
//the CONTENT flag only applies to the changed and moved from cases
if (delta.getKind() == IResourceDelta.CHANGED || (delta.getFlags() & IResourceDelta.MOVED_FROM) != 0 || (delta.getFlags() & IResourceDelta.COPIED_FROM) != 0)
delta.addFlags(IResourceDelta.CONTENT);
}
@Override
public void close(IProject project) {
delete(project);
ProposedResourceDelta delta = getDelta(project);
delta.addFlags(IResourceDelta.OPEN);
}
@Override
public void copy(IResource resource, IPath destination) {
moveOrCopyDeep(resource, destination, false /* copy */);
}
@Override
public void create(IResource resource) {
getDelta(resource).setKind(IResourceDelta.ADDED);
}
@Override
public void delete(IResource resource) {
if (resource.getType() == IResource.ROOT) {
//the root itself cannot be deleted, so create deletions for each project
IProject[] projects = ((IWorkspaceRoot) resource).getProjects(IContainer.INCLUDE_HIDDEN);
for (IProject project : projects)
buildDeleteDelta(root, project);
} else {
buildDeleteDelta(getDelta(resource.getParent()), resource);
}
}
private void fail(CoreException e) {
Policy.log(e.getStatus().getSeverity(), "An internal error occurred while accumulating a change description.", e); //$NON-NLS-1$
}
@Override
public IResourceDelta getDelta() {
return root;
}
ProposedResourceDelta getDelta(IResource resource) {
ProposedResourceDelta delta = (ProposedResourceDelta) root.findMember(resource.getFullPath());
if (delta != null) {
return delta;
}
ProposedResourceDelta parent = getDelta(resource.getParent());
delta = new ProposedResourceDelta(resource);
parent.add(delta);
return delta;
}
/*
* Return the resource at the destination path that corresponds to the source resource
* @param source the source resource
* @param sourcePrefix the path of the root of the move or copy
* @param destinationPrefix the path of the destination the root was copied to
* @return the destination resource
*/
protected IResource getDestinationResource(IResource source, IPath sourcePrefix, IPath destinationPrefix) {
IPath relativePath = source.getFullPath().removeFirstSegments(sourcePrefix.segmentCount());
IPath destinationPath = destinationPrefix.append(relativePath);
IResource destination;
IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
switch (source.getType()) {
case IResource.FILE :
destination = wsRoot.getFile(destinationPath);
break;
case IResource.FOLDER :
destination = wsRoot.getFolder(destinationPath);
break;
case IResource.PROJECT :
destination = wsRoot.getProject(destinationPath.segment(0));
break;
default :
// Shouldn't happen
destination = null;
}
return destination;
}
@Override
public void move(IResource resource, IPath destination) {
moveOrCopyDeep(resource, destination, true /* move */);
}
/**
* Builds the delta representing a single resource being moved or copied.
*
* @param resource The resource being moved
* @param sourcePrefix The root of the sub-tree being moved
* @param destinationPrefix The root of the destination sub-tree
* @param move <code>true</code> for a move, <code>false</code> for a copy
* @return Whether to move or copy the child
*/
boolean moveOrCopy(IResource resource, final IPath sourcePrefix, final IPath destinationPrefix, final boolean move) {
ProposedResourceDelta sourceDelta = getDelta(resource);
if (sourceDelta.getKind() == IResourceDelta.REMOVED) {
// There is already a removed delta here so there
// is nothing to move/copy
return false;
}
IResource destinationResource = getDestinationResource(resource, sourcePrefix, destinationPrefix);
ProposedResourceDelta destinationDelta = getDelta(destinationResource);
if ((destinationDelta.getKind() & (IResourceDelta.ADDED | IResourceDelta.CHANGED)) > 0) {
// There is already a resource at the destination
// TODO: What do we do
return false;
}
// First, create the delta for the source
IPath fromPath = resource.getFullPath();
boolean wasAdded = false;
final int sourceFlags = sourceDelta.getFlags();
if (move) {
// We transfer the source flags to the destination
if (sourceDelta.getKind() == IResourceDelta.ADDED) {
if ((sourceFlags & IResourceDelta.MOVED_FROM) != 0) {
// The resource was moved from somewhere else so
// we need to transfer the path to the new location
fromPath = sourceDelta.getMovedFromPath();
sourceDelta.setMovedFromPath(null);
}
// The source was added and then moved so we'll
// make it an add at the destination
sourceDelta.setKind(0);
wasAdded = true;
} else {
// We reset the status to be a remove/move_to
sourceDelta.setKind(IResourceDelta.REMOVED);
sourceDelta.setFlags(IResourceDelta.MOVED_TO);
sourceDelta.setMovedToPath(destinationPrefix.append(fromPath.removeFirstSegments(sourcePrefix.segmentCount())));
}
}
// Next, create the delta for the destination
if (destinationDelta.getKind() == IResourceDelta.REMOVED) {
// The destination was removed and is being re-added
destinationDelta.setKind(IResourceDelta.CHANGED);
destinationDelta.addFlags(IResourceDelta.REPLACED);
} else {
destinationDelta.setKind(IResourceDelta.ADDED);
}
if (!wasAdded || !fromPath.equals(resource.getFullPath())) {
// The source wasn't added so it is a move/copy
destinationDelta.addFlags(move ? IResourceDelta.MOVED_FROM : IResourceDelta.COPIED_FROM);
destinationDelta.setMovedFromPath(fromPath);
// Apply the source flags
if (move)
destinationDelta.addFlags(sourceFlags);
}
return true;
}
/**
* Helper method that generate a move or copy delta for a sub-tree
* of resources being moved or copied.
*/
private void moveOrCopyDeep(IResource resource, IPath destination, final boolean move) {
final IPath sourcePrefix = resource.getFullPath();
final IPath destinationPrefix = destination;
try {
//build delta for the entire sub-tree if available
if (resource.isAccessible()) {
resource.accept(child -> moveOrCopy(child, sourcePrefix, destinationPrefix, move));
} else {
//just build a delta for the single resource
moveOrCopy(resource, sourcePrefix, destination, move);
}
} catch (CoreException e) {
fail(e);
}
}
}