GP-6702: Replace some calls to FileUtilties.isPathContainedWithin() to

faster FileUtilities.startsWith() to improve startup time
This commit is contained in:
Ryan Kurtz
2026-04-13 18:26:51 -04:00
parent 85f095fe47
commit 0bf9f8ebaf
7 changed files with 58 additions and 30 deletions
@@ -293,10 +293,8 @@ public class ExtensionDetails implements Comparable<ExtensionDetails> {
// The remaining dirs are of the form:
// <repo>/Ghidra/Extensions
// <install dir>/Ghidra/Extensions
return extDirs.stream()
.skip(1)
.anyMatch(
dir -> FileUtilities.isPathContainedWithin(dir.getFile(false), installDir));
List<ResourceFile> remainingDirs = extDirs.subList(1, extDirs.size());
return FileUtilities.startsWith(remainingDirs, installDir.getAbsolutePath());
}
/**
@@ -168,10 +168,9 @@ public class PluginDescription implements Comparable<PluginDescription> {
*/
public boolean isInExtension() {
String myPath = getSourceLocation();
ResourceFile myLocation = new ResourceFile(myPath);
ApplicationLayout layout = Application.getApplicationLayout();
List<ResourceFile> extDirs = layout.getExtensionInstallationDirs();
return FileUtilities.isPathContainedWithin(extDirs, myLocation);
return FileUtilities.startsWith(extDirs, myPath);
}
/**
@@ -20,8 +20,6 @@ import java.net.*;
import java.util.HashMap;
import java.util.Map;
import utilities.util.FileUtilities;
/**
* Class for representing file object regardless of whether they are actual files in the file system or
* or files stored inside of a jar file. This class provides most all the same capabilities as the
@@ -339,13 +337,4 @@ public class ResourceFile implements Comparable<ResourceFile> {
public URI toURI() {
return resource.toURI();
}
/**
* Returns true if this file's path contains the entire path of the given file.
* @param otherFile the other file to check
* @return true if this file's path contains the entire path of the given file.
*/
public boolean containsPath(ResourceFile otherFile) {
return FileUtilities.isPathContainedWithin(getFile(false), otherFile.getFile(false));
}
}
@@ -156,7 +156,7 @@ public class GhidraApplicationLayout extends ApplicationLayout {
FileUtilities.forEachFile(extensionInstallDir, extensionDir -> {
// Skip extensions in an application root directory... already found those.
if (FileUtilities.isPathContainedWithin(applicationRootDirs, extensionDir)) {
if (FileUtilities.startsWith(applicationRootDirs, extensionDir.getAbsolutePath())) {
return;
}
@@ -286,7 +286,7 @@ public class GhidraLauncher {
for (GModule module : modules.values()) {
ResourceFile moduleDir = module.getModuleRoot();
if (!FileUtilities.isPathContainedWithin(extensionInstallationDirs, moduleDir)) {
if (!FileUtilities.startsWith(extensionInstallationDirs, moduleDir.getAbsolutePath())) {
continue; // not an extension
}
@@ -842,12 +842,50 @@ public final class FileUtilities {
}
/**
* Returns true if the given <code>potentialParentFile</code> is the parent path of
* the given <code>otherFile</code>, or if the two file paths point to the same path.
* Tests if {@code otherPath} starts with {@code potentialParentPath}. The paths are
* {@link Path#normalize() normalized} before comparing.
*
* @param potentialParentPath The path that may be the parent
* @param otherPath The path that may be the child
* @return true if the normalized {@code otherPath} starts with the normalized
* {@code potentialParentPath} and the paths are {@link Paths#get valid}; otherwise false
*/
public static boolean startsWith(String potentialParentPath, String otherPath) {
try {
return Paths.get(otherPath)
.normalize()
.startsWith(Paths.get(potentialParentPath).normalize());
}
catch (InvalidPathException e) {
return false;
}
}
/**
* Tests if {@code otherPath} starts with any of the given {@code potentialParents}. The paths
* are {@link Path#normalize() normalized} before comparing.
*
* @param potentialParents The paths that may be the parent
* @param otherPath The path that may be the child
* @return boolean true if the normalized {@code otherPath} starts with any of the given
* normalized {@code potentialParents}s and the paths are {@link Paths#get valid}; otherwise
* false
*/
public static boolean startsWith(Collection<ResourceFile> potentialParents, String otherPath) {
return potentialParents.stream().anyMatch(p -> startsWith(p.getAbsolutePath(), otherPath));
}
/**
* Returns true if the given {@code potentialParentFile} is the parent path of
* the given {@code otherFile}, or if the two file paths point to the same path.
* <p>
* NOTE: Both files are converted to their {@link File#getCanonicalPath() canonical form} prior
* to comparing their paths, which may have performance implications, particularly on Windows.
*
* @param potentialParentFile The file that may be the parent
* @param otherFile The file that may be the child
* @return boolean true if otherFile's path is within potentialParentFile's path
* @return boolean true if {@code otherFile}'s canonical path is within
* {@code potentialParentFile}'s canonical path
*/
public static boolean isPathContainedWithin(File potentialParentFile, File otherFile) {
try {
@@ -869,17 +907,21 @@ public final class FileUtilities {
}
/**
* Returns true if any of the given <code>potentialParents</code> is the parent path of or has
* the same path as the given <code>otherFile</code>.
* Returns true if any of the given {@code potentialParents} is the parent path of or has
* the same path as the given {@code otherFile}.
* <p>
* NOTE: All files are converted to their {@link File#getCanonicalPath() canonical form} prior
* to comparing their paths, which may have performance implications, particularly on Windows.
*
* @param potentialParents The files that may be the parent
* @param otherFile The file that may be the child
* @return boolean true if otherFile's path is within any of the potentialParents' paths
* @return boolean true if {@code otherFile}'s canonical path is within any of the
* {@code potentialParents}' canonical paths
*/
public static boolean isPathContainedWithin(Collection<ResourceFile> potentialParents,
ResourceFile otherFile) {
return potentialParents.stream().anyMatch(parent -> parent.containsPath(otherFile));
File f = otherFile.getFile(false);
return potentialParents.stream().anyMatch(p -> isPathContainedWithin(p.getFile(false), f));
}
/**
@@ -398,11 +398,11 @@ public class ModuleUtilities {
* directory
*/
public static boolean isExternalModule(GModule module, ApplicationLayout layout) {
File moduleRootDir = module.getModuleRoot().getFile(false);
String moduleRootPath = module.getModuleRoot().getFile(false).getAbsolutePath();
return !layout.getApplicationRootDirs()
.stream()
.map(dir -> dir.getParentFile().getFile(false))
.anyMatch(dir -> FileUtilities.isPathContainedWithin(dir, moduleRootDir));
.map(rootDir -> rootDir.getParentFile().getFile(false).getAbsolutePath())
.anyMatch(appPath -> FileUtilities.startsWith(appPath, moduleRootPath));
}
/**