Bug 576169 - Distinguish between project removal from workspace/disk
On deleting a project from Package Explorer one can have two options:
(1) Remove from the workspace ("Delete project contents on disk (cannot
be undone)" checkbox is *not* selected)
(2) Remove from the workspace and disk ("Delete project contents on disk
(cannot be undone)" checkbox is selected)
This change adds a new flag to the IResourceDelta and a new method to
IResourceChangeDescriptionFactory to differentiate the
"type" of delete ((1) or (2)) in the delta objects created by
IResourceChangeDescriptionFactory.
ResourceChangeDescriptionFactory is updated to to set this flag
accordingly & tests added for new behavior.
Change-Id: Id3036344d864fb57fd1c514dbba3780df93adbbf
Signed-off-by: Mykola Zakharchuk <zakharchuk.vn@gmail.com>
Reviewed-on: https://git.eclipse.org/r/c/platform/eclipse.platform.resources/+/185701
Tested-by: Platform Bot <platform-bot@eclipse.org>
Reviewed-by: Andrey Loskutov <loskutov@gmx.de>
diff --git a/bundles/org.eclipse.core.resources/META-INF/MANIFEST.MF b/bundles/org.eclipse.core.resources/META-INF/MANIFEST.MF
index 81cb4d2..ca6d01c 100644
--- a/bundles/org.eclipse.core.resources/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.core.resources/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.core.resources; singleton:=true
-Bundle-Version: 3.15.100.qualifier
+Bundle-Version: 3.16.0.qualifier
Bundle-Activator: org.eclipse.core.resources.ResourcesPlugin
Bundle-Vendor: %providerName
Bundle-Localization: plugin
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceChangeDescriptionFactory.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceChangeDescriptionFactory.java
index ef5c9e5..7cd068e 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceChangeDescriptionFactory.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/mapping/ResourceChangeDescriptionFactory.java
@@ -26,12 +26,25 @@
private ProposedResourceDelta root = new ProposedResourceDelta(ResourcesPlugin.getWorkspace().getRoot());
/**
- * Creates and a delta representing a deleted resource, and adds it to the provided
+ * Creates 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) {
+ return buildDeleteDelta(parentDelta, resource, false);
+ }
+
+ /**
+ * Creates 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
+ * @param deleteContent <code>true</code> if resource should be also deleted
+ * from the disk
+ */
+ private ProposedResourceDelta buildDeleteDelta(ProposedResourceDelta parentDelta, IResource resource,
+ boolean deleteContent) {
//start with the existing delta for this resource, if any, to preserve other flags
ProposedResourceDelta delta = parentDelta.getChild(resource.getName());
if (delta == null) {
@@ -39,6 +52,8 @@
parentDelta.add(delta);
}
delta.setKind(IResourceDelta.REMOVED);
+ if (deleteContent)
+ delta.addFlags(IResourceDelta.DELETE_CONTENT_PROPOSED);
if (resource.getType() == IResource.FILE)
return delta;
//recurse to build deletion deltas for children
@@ -48,7 +63,7 @@
if (childCount > 0) {
ProposedResourceDelta[] childDeltas = new ProposedResourceDelta[childCount];
for (int i = 0; i < childCount; i++)
- childDeltas[i] = buildDeleteDelta(delta, members[i]);
+ childDeltas[i] = buildDeleteDelta(delta, members[i], deleteContent);
}
} catch (CoreException e) {
//don't need to create deletion deltas for children of inaccessible resources
@@ -95,6 +110,11 @@
}
}
+ @Override
+ public void delete(IProject project, boolean deleteContent) {
+ buildDeleteDelta(root, project, deleteContent);
+ }
+
private void fail(CoreException e) {
Policy.log(e.getStatus().getSeverity(), "An internal error occurred while accumulating a change description.", e); //$NON-NLS-1$
}
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDelta.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDelta.java
index bb7989f..64ecf58 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDelta.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IResourceDelta.java
@@ -203,6 +203,19 @@
int DERIVED_CHANGED = 0x400000;
/**
+ * Change constant (bit mask) indicating that the content of the resource is
+ * proposed to be deleted. This flag can only be found in deltas created by
+ * {@link IResourceChangeDescriptionFactory} and indicates that the underlined
+ * file object is proposed to be deleted from the file system (as opposite to
+ * the change where only workspace model is deleted).
+ *
+ * @see IResourceChangeDescriptionFactory
+ * @see IResourceDelta#getFlags()
+ * @since 3.16
+ */
+ int DELETE_CONTENT_PROPOSED = 0x800000;
+
+ /**
* Accepts the given visitor.
* The only kinds of resource deltas visited
* are <code>ADDED</code>, <code>REMOVED</code>,
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IResourceChangeDescriptionFactory.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IResourceChangeDescriptionFactory.java
index 374d711..1832963 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IResourceChangeDescriptionFactory.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/mapping/IResourceChangeDescriptionFactory.java
@@ -18,11 +18,16 @@
import org.eclipse.core.runtime.IProgressMonitor;
/**
- * This factory is used to build a resource delta that represents a proposed change
- * that can then be passed to the {@link ResourceChangeValidator#validateChange(IResourceDelta, IProgressMonitor)}
- * method in order to validate the change with any model providers stored in those resources.
- * The deltas created by calls to the methods of this interface will be the same as
- * those generated by the workspace if the proposed operations were performed.
+ * This factory is used to build a resource delta that represents a proposed
+ * change that can then be passed to the
+ * {@link ResourceChangeValidator#validateChange(IResourceDelta, IProgressMonitor)}
+ * method in order to validate the change with any model providers stored in
+ * those resources. The deltas created by calls to the methods of this interface
+ * will be the same as those generated by the workspace if the proposed
+ * operations were performed, except an additional
+ * {@link IResourceDelta#DELETE_CONTENT_PROPOSED} flag can indicate if the
+ * change is going to delete resources not only from the workspace model but
+ * also physically, so it cannot be undone.
* <p>
* This factory does not validate that the proposed operation is valid given the current
* state of the resources and any other proposed changes. It only records the
@@ -69,6 +74,17 @@
void delete(IResource resource);
/**
+ * Record the set of deltas representing a deletion of the given project.
+ *
+ * @param project the project that will be deleted
+ * @param deleteContent <code>true</code> if the project content on the disk
+ * should be deleted. The content delete is not undoable.
+ * @since 3.16
+ * @see IResourceDelta#DELETE_CONTENT_PROPOSED
+ */
+ void delete(IProject project, boolean deleteContent);
+
+ /**
* Return the proposed delta that has been accumulated by this factory.
* @return the proposed delta that has been accumulated by this factory
*/
diff --git a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/mapping/AllTests.java b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/mapping/AllTests.java
index fe35c65..0b84be6 100644
--- a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/mapping/AllTests.java
+++ b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/mapping/AllTests.java
@@ -23,6 +23,9 @@
* @since 3.2
*/
@RunWith(Suite.class)
-@Suite.SuiteClasses({ ChangeValidationTest.class })
+@Suite.SuiteClasses({
+ ChangeValidationTest.class,
+ TestProjectDeletion.class
+ })
public class AllTests {
}
diff --git a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/mapping/ChangeValidationTest.java b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/mapping/ChangeValidationTest.java
index f062187..ef4b334 100644
--- a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/mapping/ChangeValidationTest.java
+++ b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/mapping/ChangeValidationTest.java
@@ -210,6 +210,37 @@
assertStatusEqual(status, new String[] {ChangeDescription.getMessageFor(ChangeDescription.REMOVED, project)});
}
+ public void testProjectDeletionWithContents() {
+ factory.delete(project, true);
+ IStatus status = validateChange(factory);
+ assertStatusEqual(status, new String[] { ChangeDescription.getMessageFor(ChangeDescription.REMOVED, project) });
+ // Check if the given delta also indicates contents deletion
+ try {
+ TestModelProvider.checkContentsDeletion = true;
+ status = validateChange(factory);
+ assertEquals("Validation should return error status on contents deletion.", IStatus.ERROR,
+ status.getSeverity());
+ } finally {
+ TestModelProvider.checkContentsDeletion = false;
+ }
+ }
+
+ public void testProjectDeletionWithoutContents() {
+ factory.delete(project, false);
+ IStatus status = validateChange(factory);
+ assertStatusEqual(status, new String[] { ChangeDescription.getMessageFor(ChangeDescription.REMOVED, project) });
+ // Check if the given delta does not indicate contents removal
+ try {
+ TestModelProvider.checkContentsDeletion = true;
+ status = validateChange(factory);
+ assertEquals("Validation should return warning status on project removal from workspace.", IStatus.WARNING,
+ status.getSeverity());
+ } finally {
+ TestModelProvider.checkContentsDeletion = false;
+ }
+
+ }
+
public void testProjectMove() {
factory.move(project, new Path("MovedProject"));
IStatus status = validateChange(factory);
diff --git a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/mapping/TestModelProvider.java b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/mapping/TestModelProvider.java
index 5671dc5..61dc2e7 100644
--- a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/mapping/TestModelProvider.java
+++ b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/mapping/TestModelProvider.java
@@ -28,6 +28,14 @@
*/
public static boolean enabled = false;
+ /**
+ * Flag to check if the given delta proposes contents deletion. If set to
+ * {@code true}, the {@link #validateChange(IResourceDelta, IProgressMonitor)}
+ * will add {@link IStatus.ERROR} to the change description to indicate contents
+ * deletion proposal.
+ */
+ public static boolean checkContentsDeletion;
+
public static final String ID = "org.eclipse.core.tests.resources.modelProvider";
@Override
@@ -46,6 +54,17 @@
} catch (CoreException e) {
description.addError(e);
}
+
+ if (checkContentsDeletion) {
+ for (IResourceDelta resourceDelta : rootDelta.getAffectedChildren()) {
+ if ((resourceDelta.getFlags() & IResourceDelta.DELETE_CONTENT_PROPOSED) != 0) {
+ // With error status we indicate contents deletion proposal in given delta.
+ description.addError(new CoreException(
+ new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, "Contents deletion proposed.")));
+ }
+ }
+ }
+
return description.asStatus();
}
}
diff --git a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/mapping/TestProjectDeletion.java b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/mapping/TestProjectDeletion.java
new file mode 100644
index 0000000..1378121
--- /dev/null
+++ b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/mapping/TestProjectDeletion.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2021 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:
+ * Mykola Zakharchuk <zakharchuk.vn@gmail.com> - Bug 576169
+ *******************************************************************************/
+package org.eclipse.core.tests.internal.mapping;
+
+import org.eclipse.core.resources.*;
+import org.eclipse.core.resources.mapping.IResourceChangeDescriptionFactory;
+import org.eclipse.core.resources.mapping.ResourceChangeValidator;
+import org.eclipse.core.tests.resources.ResourceTest;
+import org.junit.Test;
+
+/**
+ * Test to validate project kind and flags on deletion.
+ */
+public class TestProjectDeletion extends ResourceTest {
+ private IResourceChangeDescriptionFactory factory;
+ private IProject project;
+ private static int MASK = 0xFFFFFF;
+ private static int KIND_MASK = 0xFF;
+ private static int FLAGS_MASK = MASK ^= KIND_MASK;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ project = getWorkspace().getRoot().getProject("Project");
+ IResource[] resources = buildResources(project, new String[] { "a/", "a/b/", "a/c/", "a/d", "a/b/e", "a/b/f" });
+ ensureExistsInWorkspace(resources, true);
+ assertExistsInWorkspace(resources);
+ factory = ResourceChangeValidator.getValidator().createDeltaFactory();
+ int kind = factory.getDelta().getKind();
+ int flags = factory.getDelta().getFlags();
+ assertEquals("Projects delta kind should not contain any bits before refactoring.", 0, kind &= ~KIND_MASK);
+ assertEquals("Projects delta flags should not be set before refactoring.", 0, flags &= ~FLAGS_MASK);
+ }
+
+ @Test
+ public void testDeletionWithContents() {
+ testDeletion(true);
+ }
+
+ @Test
+ public void testDeletionWithoutContents() {
+ testDeletion(false);
+ }
+
+ private void testDeletion(boolean deleteContents) {
+ factory.delete(project, deleteContents);
+ checkAffectedChildrenStatus(factory.getDelta().getAffectedChildren(), deleteContents);
+ }
+
+ private void checkAffectedChildrenStatus(IResourceDelta[] affectedChildren, boolean deleteContents) {
+ for (IResourceDelta iResourceDelta : affectedChildren) {
+ assertEquals("IResourceDelta.REMOVED kind is expected on project deletion.", IResourceDelta.REMOVED,
+ iResourceDelta.getKind());
+ if (deleteContents) {
+ assertEquals("IResourceDelta.DELETE_CONTENT_PROPOSED flag should be set on project contents deletion.",
+ IResourceDelta.DELETE_CONTENT_PROPOSED,
+ iResourceDelta.getFlags());
+ } else {
+ assertEquals("No flags should be set on project deletion from workspace.", 0,
+ iResourceDelta.getFlags());
+ }
+ checkAffectedChildrenStatus(iResourceDelta.getAffectedChildren(), deleteContents);
+ }
+ }
+}