blob: 733019f883492d878bc7e52f58bbb3e7f8406ba3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2016 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
*******************************************************************************/
package org.eclipse.ltk.internal.core.refactoring.resource;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.mapping.IResourceChangeDescriptionFactory;
import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.LocationKind;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.ltk.core.refactoring.RefactoringChangeDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
import org.eclipse.ltk.core.refactoring.participants.DeleteArguments;
import org.eclipse.ltk.core.refactoring.participants.DeleteParticipant;
import org.eclipse.ltk.core.refactoring.participants.DeleteProcessor;
import org.eclipse.ltk.core.refactoring.participants.ParticipantManager;
import org.eclipse.ltk.core.refactoring.participants.RefactoringParticipant;
import org.eclipse.ltk.core.refactoring.participants.ResourceChangeChecker;
import org.eclipse.ltk.core.refactoring.participants.SharableParticipants;
import org.eclipse.ltk.core.refactoring.resource.DeleteResourceChange;
import org.eclipse.ltk.core.refactoring.resource.DeleteResourcesDescriptor;
import org.eclipse.ltk.internal.core.refactoring.BasicElementLabels;
import org.eclipse.ltk.internal.core.refactoring.Messages;
import org.eclipse.ltk.internal.core.refactoring.RefactoringCoreMessages;
import org.eclipse.ltk.internal.core.refactoring.Resources;
/**
* A delete processor for {@link IResource resources}. The processor will delete the resources and
* load delete participants if references should be deleted as well.
*
* @since 3.4
*/
public class DeleteResourcesProcessor extends DeleteProcessor {
private IResource[] fResources;
private boolean fDeleteContents;
/**
* Create a new delete processor. Same as DeleteResourcesProcessor(resources, true, false)
* @param resources the resources to delete. They can be either {@link IProject} or {@link IFile} and {@link IFolder}.
*/
public DeleteResourcesProcessor(IResource[] resources) {
this(resources, false);
}
/**
* Create a new delete processor.
* @param resources the resources to delete. They can be either {@link IProject} or {@link IFile} and {@link IFolder}.
* @param deleteContents <code>true</code> if this will delete the project contents. The content delete is not undoable.
*/
public DeleteResourcesProcessor(IResource[] resources, boolean deleteContents) {
fResources= removeDescendants(resources);
fDeleteContents= deleteContents;
}
/**
* Returns the resources to delete.
*
* @return the resources to delete.
*/
public IResource[] getResourcesToDelete() {
return fResources;
}
/**
* Delete projects contents.
* @return <code>true</code> if this will delete the project contents. The content delete is not undoable.
*/
public boolean isDeleteContents() {
return fDeleteContents;
}
/**
* Set to delete the projects content.
* @param deleteContents <code>true</code> if this will delete the project contents. The content delete is not undoable.
*/
public void setDeleteContents(boolean deleteContents) {
fDeleteContents= deleteContents;
}
@Override
public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException {
// allow only projects or only non-projects to be selected;
// note that the selection may contain multiple types of resource
if (!(Resources.containsOnlyProjects(fResources) || Resources.containsOnlyNonProjects(fResources))) {
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.DeleteResourcesProcessor_delete_error_mixed_types);
}
return new RefactoringStatus();
}
@Override
public RefactoringStatus checkFinalConditions(IProgressMonitor pm, CheckConditionsContext context) throws CoreException, OperationCanceledException {
pm.beginTask("", 1); //$NON-NLS-1$
try {
RefactoringStatus result= new RefactoringStatus();
for (int i= 0; i < fResources.length; i++) {
IResource resource= fResources[i];
if (!isSynchronizedExcludingLinkedResources(resource)) {
String pathLabel= BasicElementLabels.getPathLabel(resource.getFullPath(), false);
String locationLabel= null;
IPath location= resource.getLocation();
if (location != null) {
locationLabel= BasicElementLabels.getPathLabel(location, true);
} else {
URI uri= resource.getLocationURI();
if (uri != null) {
locationLabel= BasicElementLabels.getURLPart(uri.toString());
}
}
String warning;
if (resource instanceof IFile) {
if (locationLabel != null) {
warning= Messages.format(RefactoringCoreMessages.DeleteResourcesProcessor_warning_out_of_sync_file_loc, new Object[] { pathLabel, locationLabel });
} else {
warning= Messages.format(RefactoringCoreMessages.DeleteResourcesProcessor_warning_out_of_sync_file, pathLabel);
}
} else {
if (locationLabel != null) {
warning= Messages.format(RefactoringCoreMessages.DeleteResourcesProcessor_warning_out_of_sync_container_loc, new Object[] { pathLabel, locationLabel });
} else {
warning= Messages.format(RefactoringCoreMessages.DeleteResourcesProcessor_warning_out_of_sync_container, pathLabel);
}
}
result.addWarning(warning);
}
}
checkDirtyResources(result);
ResourceChangeChecker checker= context.getChecker(ResourceChangeChecker.class);
IResourceChangeDescriptionFactory deltaFactory= checker.getDeltaFactory();
for (int i= 0; i < fResources.length; i++) {
if (fResources[i].isPhantom()) {
result.addFatalError(Messages.format(RefactoringCoreMessages.DeleteResourcesProcessor_delete_error_phantom, BasicElementLabels.getPathLabel(fResources[i].getFullPath(), false)));
} else if (fDeleteContents && Resources.isReadOnly(fResources[i])) {
result.addFatalError(Messages.format(RefactoringCoreMessages.DeleteResourcesProcessor_delete_error_read_only, BasicElementLabels.getPathLabel(fResources[i].getFullPath(), false)));
} else {
deltaFactory.delete(fResources[i]);
}
}
return result;
} finally {
pm.done();
}
}
/**
* Checks whether this resource and its descendents are considered to be in sync with the local
* file system. The linked resources and their descendents are excluded from the check.
*
* @param resource the resource to check
* @return <code>true</code> if this resource and its descendents except linked resources are
* synchronized, and <code>false</code> in all other cases
* @throws CoreException if visiting the resource descendents fails for any reason
* @see IResource#isSynchronized(int)
*/
public boolean isSynchronizedExcludingLinkedResources(IResource resource) throws CoreException {
boolean[] result= { true };
resource.accept(new IResourceVisitor() {
@Override
public boolean visit(IResource visitedResource) throws CoreException {
if (!result[0] || visitedResource.isLinked())
return false;
if (!visitedResource.isSynchronized(IResource.DEPTH_ZERO)) {
result[0]= false;
return false;
}
return true;
}
}, IResource.DEPTH_INFINITE, IContainer.DO_NOT_CHECK_EXISTENCE);
return result[0];
}
private void checkDirtyResources(final RefactoringStatus result) throws CoreException {
for (int i= 0; i < fResources.length; i++) {
IResource resource= fResources[i];
if (resource instanceof IProject && !((IProject) resource).isOpen())
continue;
resource.accept(new IResourceVisitor() {
@Override
public boolean visit(IResource visitedResource) throws CoreException {
if (visitedResource instanceof IFile) {
checkDirtyFile(result, (IFile)visitedResource);
}
return true;
}
}, IResource.DEPTH_INFINITE, false);
}
}
private void checkDirtyFile(RefactoringStatus result, IFile file) {
if (!file.exists())
return;
ITextFileBuffer buffer= FileBuffers.getTextFileBufferManager().getTextFileBuffer(file.getFullPath(), LocationKind.IFILE);
if (buffer != null && buffer.isDirty()) {
String message= RefactoringCoreMessages.DeleteResourcesProcessor_warning_unsaved_file;
if (buffer.isStateValidated() && buffer.isSynchronized()) {
result.addWarning(Messages.format(message, BasicElementLabels.getPathLabel(file.getFullPath(), false)));
} else {
result.addFatalError(Messages.format(message, BasicElementLabels.getPathLabel(file.getFullPath(), false)));
}
}
}
@Override
public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException {
pm.beginTask(RefactoringCoreMessages.DeleteResourcesProcessor_create_task, fResources.length);
try {
RefactoringChangeDescriptor descriptor= new RefactoringChangeDescriptor(createDescriptor());
CompositeChange change= new CompositeChange(RefactoringCoreMessages.DeleteResourcesProcessor_change_name);
change.markAsSynthetic();
for (int i= 0; i < fResources.length; i++) {
pm.worked(1);
DeleteResourceChange dc= new DeleteResourceChange(fResources[i].getFullPath(), true, fDeleteContents);
dc.setDescriptor(descriptor);
change.add(dc);
}
return change;
} finally {
pm.done();
}
}
protected DeleteResourcesDescriptor createDescriptor() {
DeleteResourcesDescriptor descriptor= new DeleteResourcesDescriptor();
descriptor.setProject(null);
descriptor.setDescription(getDeleteDescription());
descriptor.setComment(descriptor.getDescription());
descriptor.setFlags(RefactoringDescriptor.STRUCTURAL_CHANGE | RefactoringDescriptor.MULTI_CHANGE | RefactoringDescriptor.BREAKING_CHANGE);
descriptor.setDeleteContents(fDeleteContents);
descriptor.setResources(fResources);
return descriptor;
}
private String getDeleteDescription() {
if (fResources.length == 1) {
return Messages.format(RefactoringCoreMessages.DeleteResourcesProcessor_description_single, BasicElementLabels.getPathLabel(fResources[0].getFullPath(), false));
}
return Messages.format(RefactoringCoreMessages.DeleteResourcesProcessor_description_multi, new Integer(fResources.length));
}
@Override
public Object[] getElements() {
return fResources;
}
@Override
public String getIdentifier() {
return "org.eclipse.ltk.core.refactoring.deleteResourcesProcessor"; //$NON-NLS-1$
}
@Override
public String getProcessorName() {
return RefactoringCoreMessages.DeleteResourcesProcessor_processor_name;
}
@Override
public boolean isApplicable() throws CoreException {
for (int i= 0; i < fResources.length; i++) {
if (!canDelete(fResources[i])) {
return false;
}
}
return true;
}
private boolean canDelete(IResource res) {
return res.isAccessible() && !res.isPhantom();
}
@Override
public RefactoringParticipant[] loadParticipants(RefactoringStatus status, SharableParticipants sharedParticipants) throws CoreException {
final ArrayList<DeleteParticipant> result= new ArrayList<>();
if (!isApplicable()) {
return new RefactoringParticipant[0];
}
final String[] affectedNatures= ResourceProcessors.computeAffectedNatures(fResources);
final DeleteArguments deleteArguments= new DeleteArguments(fDeleteContents);
for (int i= 0; i < fResources.length; i++) {
result.addAll(Arrays.asList(ParticipantManager.loadDeleteParticipants(status, this, fResources[i], deleteArguments, affectedNatures, sharedParticipants)));
}
return result.toArray(new RefactoringParticipant[result.size()]);
}
private static IResource[] removeDescendants(IResource[] resources) {
ArrayList<IResource> result= new ArrayList<>();
for (int i= 0; i < resources.length; i++) {
addToList(result, resources[i]);
}
return result.toArray(new IResource[result.size()]);
}
private static void addToList(ArrayList<IResource> result, IResource curr) {
IPath currPath= curr.getFullPath();
for (int k= result.size() - 1; k >= 0 ; k--) {
IResource other= result.get(k);
IPath otherPath= other.getFullPath();
if (otherPath.isPrefixOf(currPath)) {
return; // current entry is a descendant of an entry in the list
}
if (currPath.isPrefixOf(otherPath)) {
result.remove(k); // entry in the list is a descendant of the current entry
}
}
result.add(curr);
}
}