From 02a72e3a563ba2807bcbf6fa26f8569988d885a3 Mon Sep 17 00:00:00 2001 From: Federico Jeanne Date: Wed, 23 Sep 2026 14:51:25 +0200 Subject: [PATCH 1/2] Speed up FileSystemResourceManager.resourceForLocation() Check the cheap segment counts before the more expensive IPath.isPrefixOf() and stop iterating over the projects as soon as a project location equal to the given location has been matched, since no other project can provide a better match. Add tests covering project locations, nested projects in both iteration orders, filtered resources in nested projects, hidden projects and project names being plain string prefixes of each other. Co-Authored-By: Claude Opus 5.5 --- .../localstore/FileSystemResourceManager.java | 32 +++-- .../tests/resources/IWorkspaceRootTest.java | 133 +++++++++++++++++- 2 files changed, 152 insertions(+), 13 deletions(-) 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/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 */ From bd0b5df6c0829c1e0a990d2f45f46943b8718e9d Mon Sep 17 00:00:00 2001 From: Federico Jeanne Date: Wed, 23 Sep 2026 16:23:09 +0200 Subject: [PATCH 2/2] Bump version for 'org.eclipse.core.tests.resources' Let it match the current version of 'org.eclipse.core.resources', according to /org.eclipse.platform.docs/VersionNumbering.md (Line 77): "In particular, a source/test/documentation plug-in that has changes in the current stream should evolve its version number in sync with the plug-in(s) it is providing source/test/documentation for." --- .../tests/org.eclipse.core.tests.resources/META-INF/MANIFEST.MF | 2 +- resources/tests/org.eclipse.core.tests.resources/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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