diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java index e39ab8a650d..6a84fba1f07 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/FileSystemResourceManager.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2025 IBM Corporation and others. + * Copyright (c) 2000, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -372,20 +372,28 @@ private IResource resourceForLocation(IPath location, boolean files) { } return null; } + int locationSegments = location.segmentCount(); int resultProjectPathSegments = 0; IResource result = null; - IProject[] projects = getWorkspace().getRoot().getProjects(IContainer.INCLUDE_HIDDEN); - for (IProject project : projects) { + for (IProject project : workspace.getRoot().getProjects(IContainer.INCLUDE_HIDDEN)) { IPath projectLocation = project.getLocation(); - if (projectLocation != null && projectLocation.isPrefixOf(location)) { - int segmentsToRemove = projectLocation.segmentCount(); - if (segmentsToRemove > resultProjectPathSegments) { - IPath path = project.getFullPath().append(location.removeFirstSegments(segmentsToRemove)); - IResource resource = resourceFor(path, files); - if (resource != null && !((Resource) resource).isFiltered()) { - resultProjectPathSegments = segmentsToRemove; - result = resource; - } + if (projectLocation == null) { + continue; + } + int segmentsToRemove = projectLocation.segmentCount(); + // cheap segment count checks first, the prefix check is more expensive + if (segmentsToRemove <= resultProjectPathSegments || segmentsToRemove > locationSegments + || !projectLocation.isPrefixOf(location)) { + continue; + } + IPath path = project.getFullPath().append(location.removeFirstSegments(segmentsToRemove)); + IResource resource = resourceFor(path, files); + if (resource != null && !((Resource) resource).isFiltered()) { + resultProjectPathSegments = segmentsToRemove; + result = resource; + if (segmentsToRemove == locationSegments) { + // no other project can have a longer matching location + break; } } } diff --git a/resources/tests/org.eclipse.core.tests.resources/META-INF/MANIFEST.MF b/resources/tests/org.eclipse.core.tests.resources/META-INF/MANIFEST.MF index 67609896715..ca5940c0488 100644 --- a/resources/tests/org.eclipse.core.tests.resources/META-INF/MANIFEST.MF +++ b/resources/tests/org.eclipse.core.tests.resources/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Eclipse Core Tests Resources Bundle-SymbolicName: org.eclipse.core.tests.resources; singleton:=true -Bundle-Version: 3.11.1400.qualifier +Bundle-Version: 3.24.200.qualifier Bundle-Vendor: Eclipse.org Export-Package: org.eclipse.core.tests.filesystem, org.eclipse.core.tests.internal.alias, diff --git a/resources/tests/org.eclipse.core.tests.resources/pom.xml b/resources/tests/org.eclipse.core.tests.resources/pom.xml index 2242d27669e..865de202b26 100644 --- a/resources/tests/org.eclipse.core.tests.resources/pom.xml +++ b/resources/tests/org.eclipse.core.tests.resources/pom.xml @@ -18,7 +18,7 @@ 4.42.0-SNAPSHOT org.eclipse.core.tests.resources - 3.11.1400-SNAPSHOT + 3.24.200-SNAPSHOT eclipse-test-plugin diff --git a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/IWorkspaceRootTest.java b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/IWorkspaceRootTest.java index ff799d7cd58..d0e7cf05f36 100644 --- a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/IWorkspaceRootTest.java +++ b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/IWorkspaceRootTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2023 IBM Corporation and others. + * Copyright (c) 2000, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -34,12 +34,14 @@ import org.eclipse.core.filesystem.IFileStore; import org.eclipse.core.filesystem.URIUtil; import org.eclipse.core.internal.resources.Workspace; +import org.eclipse.core.resources.FileInfoMatcherDescription; 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.IProjectDescription; import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceFilterDescription; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.core.runtime.CoreException; @@ -414,6 +416,135 @@ public void testBug476585() throws CoreException { assertEquals(subProject, container.getProject()); } + /** + * The location of a project itself maps to the project as container and to + * no file. + */ + @Test + public void testGetForLocationOfProjectLocation() throws CoreException { + IWorkspaceRoot root = getWorkspace().getRoot(); + IProject project = root.getProject("p"); + createInWorkspace(project); + + assertEquals(project, root.getContainerForLocation(project.getLocation())); + assertNull(root.getFileForLocation(project.getLocation())); + } + + /** + * Locations outside of any project do not map to any resource. + */ + @Test + public void testGetForLocationOutsideOfProjects() throws CoreException { + IWorkspaceRoot root = getWorkspace().getRoot(); + IProject project = root.getProject("p"); + createInWorkspace(project); + + IPath outside = root.getLocation().append("notAProject").append("file.txt"); + assertNull(root.getFileForLocation(outside)); + assertNull(root.getContainerForLocation(outside)); + } + + /** + * A project location must be a segment-wise prefix of the given location, a + * plain string prefix (e.g. "p" for "p2") must not match. + */ + @Test + public void testGetForLocationWithStringPrefixProjectName() throws CoreException { + IWorkspaceRoot root = getWorkspace().getRoot(); + IProject project = root.getProject("p"); + IProject project2 = root.getProject("p2"); + createInWorkspace(new IResource[] { project, project2 }); + + IPath fileLocation = project2.getLocation().append("file.txt"); + assertEquals(project2.getFile("file.txt"), root.getFileForLocation(fileLocation)); + assertEquals(project2.getFolder("file.txt"), root.getContainerForLocation(fileLocation)); + } + + /** + * The nested project wins regardless of the order in which projects are + * iterated (see also {@link #testBug476585()} for the opposite order). + */ + @Test + public void testGetForLocationNestedProjectIteratedFirst() throws CoreException { + IWorkspaceRoot root = getWorkspace().getRoot(); + IProject outer = root.getProject("z_outer"); + createInWorkspace(outer); + IProject inner = createProjectAt("a_inner", outer.getLocation().append("nested")); + + IPath fileLocation = inner.getLocation().append("folder").append("file.txt"); + assertEquals(inner.getFile("folder/file.txt"), root.getFileForLocation(fileLocation)); + assertEquals(inner.getFolder("folder/file.txt"), root.getContainerForLocation(fileLocation)); + } + + /** + * The location of a nested project maps to the nested project as container, + * but to a file of the enclosing project, since a project location can never + * be a file of the nested project itself. + */ + @Test + public void testGetForLocationOfNestedProjectLocation() throws CoreException { + IWorkspaceRoot root = getWorkspace().getRoot(); + for (String[] names : new String[][] { { "a_outer", "z_inner" }, { "z_outer", "a_inner" } }) { + IProject outer = root.getProject(names[0]); + createInWorkspace(outer); + IProject inner = createProjectAt(names[1], outer.getLocation().append("nested")); + + assertEquals(inner, root.getContainerForLocation(inner.getLocation())); + assertEquals(outer.getFile("nested"), root.getFileForLocation(inner.getLocation())); + + removeFromWorkspace(new IResource[] { inner, outer }); + } + } + + /** + * If the resource in the nested project is filtered out, the resource of the + * enclosing project is returned instead. + */ + @Test + public void testGetForLocationFilteredInNestedProject() throws CoreException { + IWorkspaceRoot root = getWorkspace().getRoot(); + for (String[] names : new String[][] { { "a_outer", "z_inner" }, { "z_outer", "a_inner" } }) { + IProject outer = root.getProject(names[0]); + createInWorkspace(outer); + IProject inner = createProjectAt(names[1], outer.getLocation().append("nested")); + inner.createFilter( + IResourceFilterDescription.EXCLUDE_ALL | IResourceFilterDescription.FOLDERS + | IResourceFilterDescription.FILES | IResourceFilterDescription.INHERITABLE, + new FileInfoMatcherDescription("org.eclipse.core.resources.regexFilterMatcher", "folder"), 0, + createTestMonitor()); + + IPath fileLocation = inner.getLocation().append("folder").append("file.txt"); + assertEquals(outer.getFile("nested/folder/file.txt"), root.getFileForLocation(fileLocation)); + assertEquals(outer.getFolder("nested/folder/file.txt"), root.getContainerForLocation(fileLocation)); + + removeFromWorkspace(new IResource[] { inner, outer }); + } + } + + /** + * Files and folders of hidden projects are found as well. + */ + @Test + public void testGetForLocationInHiddenProject() throws CoreException { + IWorkspaceRoot root = getWorkspace().getRoot(); + IProject hiddenProject = root.getProject(createUniqueString()); + hiddenProject.create(null, IResource.HIDDEN, createTestMonitor()); + hiddenProject.open(createTestMonitor()); + + IPath fileLocation = hiddenProject.getLocation().append("file.txt"); + assertEquals(hiddenProject.getFile("file.txt"), root.getFileForLocation(fileLocation)); + assertEquals(hiddenProject.getFolder("file.txt"), root.getContainerForLocation(fileLocation)); + } + + private IProject createProjectAt(String name, IPath location) throws CoreException { + IProject project = getWorkspace().getRoot().getProject(name); + IProjectDescription description = getWorkspace().newProjectDescription(name); + description.setLocation(location); + project.create(description, createTestMonitor()); + project.open(createTestMonitor()); + return project; + } + /* * see bug 232765 for details */