From fc5d071b14a33e8672dc0172ca96fff142b347ca Mon Sep 17 00:00:00 2001 From: Simeon Andreev Date: Thu, 1 Jun 2023 11:29:19 +0300 Subject: [PATCH] Fix stale resource link flags after project re-open Whenever a project is closed, its resource tree is saved. This includes linked resources in the project. When the project is re-opened, link changes in the .project file are not reflected on the projects resource tree. The old resource tree is read, new information is stored in ProjectDescription.linkDescriptions, but the old linked resources are not touched. This change adjusts Project.open() and Project.close() to set resp. clear the M_LINK flag of linked resources in the project. Fixes: #470 Signed-off-by: Simeon Andreev --- .../core/internal/resources/Project.java | 39 ++++++ .../tests/resources/AllResourcesTests.java | 1 + .../tests/resources/ProjectLinksTest.java | 131 ++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/ProjectLinksTest.java diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java index 872223f6201..d6da9d8c47d 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.Map; import java.util.TreeSet; +import java.util.function.Consumer; import org.eclipse.core.filesystem.EFS; import org.eclipse.core.filesystem.IFileInfo; import org.eclipse.core.filesystem.IFileStore; @@ -232,6 +233,11 @@ public void close(IProgressMonitor monitor) throws CoreException { workspace.broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_CLOSE, this)); // flush the build order early in case there is a problem workspace.flushBuildOrder(); + // Clear the linked flag of linked resources, + // since changes to the links in .project while the project is closed + // are otherwise not reflected after re-opening the project. + // See: https://github.com/eclipse-platform/eclipse.platform/issues/470 + clearLinkFlags(); IProgressMonitor sub = subMonitor.newChild(49, SubMonitor.SUPPRESS_SUBTASK); IStatus saveStatus = workspace.getSaveManager().save(ISaveContext.PROJECT_SAVE, this, sub); internalClose(subMonitor.newChild(49)); @@ -1149,6 +1155,9 @@ public void open(int updateFlags, IProgressMonitor monitor) throws CoreException writeEncodingAfterOpen(monitor); encodingWritten = true; } + if (used) { + setLinkFlags(); + } //creation of this project may affect overlapping resources workspace.getAliasManager().updateAliases(this, getStore(), IResource.DEPTH_INFINITE, monitor); } catch (OperationCanceledException e) { @@ -1488,6 +1497,36 @@ public String getDefaultLineSeparator() { return System.lineSeparator(); } + /** + * Clears the {@link ICoreConstants#M_LINK} flag of linked resources. + */ + private void clearLinkFlags() { + modifyLinksResourceInfo(info -> info.clear(M_LINK)); + } + + /** + * Sets the {@link ICoreConstants#M_LINK} flag of linked resources. + */ + private void setLinkFlags() { + modifyLinksResourceInfo(info -> info.set(M_LINK)); + } + + private void modifyLinksResourceInfo(Consumer operation) { + ProjectDescription description = internalGetDescription(); + HashMap linkDescriptions = description.linkDescriptions; + if (linkDescriptions != null) { + for (LinkDescription linkDescription : linkDescriptions.values()) { + IFile linkFile = getFile(linkDescription.getProjectRelativePath()); + if (linkFile != null) { + ResourceInfo linkInfo = workspace.getResourceInfo(linkFile.getFullPath(), false, true); + if (linkInfo != null) { + operation.accept(linkInfo); + } + } + } + } + } + private static String getLineSeparatorFromPreferences(Preferences node) { try { // be careful looking up for our node so not to create any nodes as side effect diff --git a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/AllResourcesTests.java b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/AllResourcesTests.java index 5861437b2ee..3dd5cde2a8f 100644 --- a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/AllResourcesTests.java +++ b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/AllResourcesTests.java @@ -52,6 +52,7 @@ TeamPrivateMemberTest.class, // VirtualFolderTest.class, // WorkspaceTest.class, // + ProjectLinksTest.class, // }) public class AllResourcesTests { diff --git a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/ProjectLinksTest.java b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/ProjectLinksTest.java new file mode 100644 index 00000000000..77e661dcdb6 --- /dev/null +++ b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/ProjectLinksTest.java @@ -0,0 +1,131 @@ +/******************************************************************************* + * Copyright (c) 2023 Simeon Andreev 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: + * Simeon Andreev - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.tests.resources; + +import static org.eclipse.core.tests.harness.FileSystemHelper.getRandomLocation; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +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.IWorkspace; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.tests.harness.FussyProgressMonitor; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +/** + * + */ +public class ProjectLinksTest { + + @Rule + public WorkspaceTestRule workspaceRule = new WorkspaceTestRule(); + + private IProject project; + private Path tmpFolder; + private IPath tmpPath; + + @Before + public void setUp() throws Exception { + tmpPath = getRandomLocation(); + tmpFolder = Paths.get(tmpPath.toOSString()); + Files.createDirectory(tmpFolder); + + IWorkspaceRoot root = getWorkspace().getRoot(); + project = root.getProject(getUniqueString()); + + project.create(getMonitor()); + project.open(getMonitor()); + project.refreshLocal(IResource.DEPTH_INFINITE, getMonitor()); + } + + @After + public void tearDown() throws Exception { + Files.deleteIfExists(tmpFolder); + project.delete(true, getMonitor()); + } + + /** + * Tests that link information is updated after closing a project, deleting a + * link in the {@code .project} file and then opening the project. + */ + @Test + public void testCloseProjectDeleteLinksAndOpen_GH470() throws Exception { + IFile dotProject = project.getFile(".project"); + Path dotProjectPath = Paths.get(dotProject.getLocationURI()); + List dotProjectContentsWithoutLink = Files.readAllLines(dotProjectPath); + + String linkedFolderName = "test"; + IFolder folder = project.getFolder(linkedFolderName); + folder.createLink(tmpPath, IResource.NONE, getMonitor()); + project.refreshLocal(IResource.DEPTH_INFINITE, getMonitor()); + + assertTrue("Failed to create linked folder in test project", folder.isLinked()); + + project.close(getMonitor()); + + Files.write(dotProjectPath, dotProjectContentsWithoutLink); + + project.open(getMonitor()); + project.refreshLocal(IResource.DEPTH_INFINITE, getMonitor()); + + folder = project.getFolder(linkedFolderName); + assertFalse("Expected folder to not be linked after re-opening project", folder.isLinked()); + } + + /** + * Tests that link information is correct after closing a project and then + * opening the project. + */ + public void testCloseAndOpenProject() throws Exception { + String linkedFolderName = "test"; + IFolder folder = project.getFolder(linkedFolderName); + folder.createLink(tmpPath, IResource.NONE, getMonitor()); + project.refreshLocal(IResource.DEPTH_INFINITE, getMonitor()); + + assertTrue("Failed to create linked folder in test project", folder.isLinked()); + + project.close(getMonitor()); + + project.open(getMonitor()); + project.refreshLocal(IResource.DEPTH_INFINITE, getMonitor()); + + folder = project.getFolder(linkedFolderName); + assertTrue("Expected folder to be linked after re-opening project", folder.isLinked()); + } + + static IProgressMonitor getMonitor() { + return new FussyProgressMonitor(); + } + + public static IWorkspace getWorkspace() { + return ResourcesPlugin.getWorkspace(); + } + + public String getUniqueString() { + return System.nanoTime() + "-" + Math.random(); + } + +}