This is an automated email from the ASF dual-hosted git repository. bodewig pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/ant.git
commit 198c8ea1b0e5cedcdc462927c3c9c54010d04ec3 Author: Stefan Bodewig <[email protected]> AuthorDate: Sun Mar 1 12:25:54 2026 +0100 make resolvePath work for not existing files as good as possible --- src/main/org/apache/tools/ant/util/FileUtils.java | 34 +++++-- .../org/apache/tools/ant/util/FileUtilsTest.java | 101 +++++++++++++++++++++ 2 files changed, 128 insertions(+), 7 deletions(-) diff --git a/src/main/org/apache/tools/ant/util/FileUtils.java b/src/main/org/apache/tools/ant/util/FileUtils.java index dbc974980..d51218dd7 100644 --- a/src/main/org/apache/tools/ant/util/FileUtils.java +++ b/src/main/org/apache/tools/ant/util/FileUtils.java @@ -46,6 +46,7 @@ import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; +import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Optional; @@ -88,8 +89,12 @@ public class FileUtils { private static final boolean ON_WIN9X = Os.isFamily("win9x"); private static final boolean ON_WINDOWS = Os.isFamily("windows"); + // see https://bugs.openjdk.org/browse/JDK-8003887 private static final boolean CAN_TRUST_GET_CANONICAL_PATH = !ON_WINDOWS || JavaEnvUtils.isAtLeastJavaVersion("24"); + // bug report opened with OpenJDK but hasn't been published, yet + private static final boolean CAN_TRUST_GET_CANONICAL_PATH_FOR_NOT_EXISTING_FILES = + !ON_WINDOWS; static final int BUF_SIZE = 8192; @@ -2015,15 +2020,30 @@ public class FileUtils { * @since Ant 1.10.16 */ public String getResolvedPath(File f) throws IOException { - if (!CAN_TRUST_GET_CANONICAL_PATH) { - try { - return f.toPath().toRealPath().toString(); - } catch (FileNotFoundException ex) { - // file or link target doesn't exist, fall back to getCanonicalPath - } catch (NoSuchFileException ex) { - // file or link target doesn't exist, fall back to getCanonicalPath + if (CAN_TRUST_GET_CANONICAL_PATH_FOR_NOT_EXISTING_FILES + || (CAN_TRUST_GET_CANONICAL_PATH && f.exists())) { + return f.getCanonicalPath(); + } + if (f.exists()) { + return f.toPath().toRealPath().toString(); + } + LinkedList<String> trailer = new LinkedList<>(); + trailer.push(f.getName()); + File parent = f.getParentFile(); + while (parent != null) { + if (parent.exists()) { + String resolvedParent = getResolvedPath(parent); + String[] parentStack = getPathStack(resolvedParent); + List<String> rebuilt = + new ArrayList<String>(Arrays.asList(parentStack)); + rebuilt.addAll(trailer); + return getPath(rebuilt, File.separatorChar); } + trailer.push(parent.getName()); + parent = parent.getParentFile(); } + // reached root of file system without ever encountering + // an existing directory. Giving up. return f.getCanonicalPath(); } } diff --git a/src/tests/junit/org/apache/tools/ant/util/FileUtilsTest.java b/src/tests/junit/org/apache/tools/ant/util/FileUtilsTest.java index bdf1f3245..2d9bd0b1d 100644 --- a/src/tests/junit/org/apache/tools/ant/util/FileUtilsTest.java +++ b/src/tests/junit/org/apache/tools/ant/util/FileUtilsTest.java @@ -35,6 +35,8 @@ import org.apache.tools.ant.BuildException; import org.apache.tools.ant.MagicTestNames; import org.apache.tools.ant.Project; import org.apache.tools.ant.taskdefs.condition.Os; +import org.apache.tools.ant.taskdefs.condition.CanCreateSymbolicLink; +import org.apache.tools.ant.taskdefs.optional.windows.Mklink; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -70,6 +72,8 @@ public class FileUtilsTest { @Rule public TemporaryFolder folder = new TemporaryFolder(); + private static final boolean CAN_CREATE_SYMLINKS = new CanCreateSymbolicLink().eval(); + private static final String ROOT = System.getProperty(MagicTestNames.TEST_ROOT_DIRECTORY); private String root; @@ -879,6 +883,92 @@ public class FileUtilsTest { assertEquals("file:/foo", getFileUtils().stripLeadingPathSeparator("file:/foo")); } + @Test + public void getResolvedPathWorksForNormalFilesThatExist() throws IOException { + File f = folder.newFile(); + String resolvedPath = getFileUtils().getResolvedPath(f); + assertEquals(f.getAbsolutePath(), resolvedPath); + } + + @Test + public void getResolvedPathWorksForNormalFilesThatDoesntExist() throws IOException { + File missingDir = new File(folder.getRoot(), "foo"); + File f = new File(missingDir, "foo"); + String resolvedPath = getFileUtils().getResolvedPath(f); + assertEquals(f.getAbsolutePath(), resolvedPath); + } + + @Test + public void getResolvedPathWorksForSymlinksThatExist() throws IOException { + assumeTrue("setup doesn't support creation of symbolic links", + CAN_CREATE_SYMLINKS); + File existingDir = folder.newFolder(); + File f = new File(folder.getRoot(), "foo"); + Files.createSymbolicLink(f.toPath(), existingDir.toPath()); + String resolvedPath = getFileUtils().getResolvedPath(f); + assertEquals(existingDir.getAbsolutePath(), resolvedPath); + } + + @Test + public void getResolvedPathDoesntResolveDanglingSymlinks() throws IOException { + assumeTrue("setup doesn't support creation of symbolic links", + CAN_CREATE_SYMLINKS); + File missingDir = new File(folder.getRoot(), "foo"); + File f = new File(folder.getRoot(), "bar"); + Files.createSymbolicLink(f.toPath(), missingDir.toPath()); + String resolvedPath = getFileUtils().getResolvedPath(f); + assertEquals(f.getAbsolutePath(), resolvedPath); + } + + @Test + public void getResolvedPathWorksForNotExistingFilesInExistingSymlinkDirectories() throws IOException { + assumeTrue("setup doesn't support creation of symbolic links", + CAN_CREATE_SYMLINKS); + File existingDir = folder.newFolder(); + File link = new File(folder.getRoot(), "foo"); + Files.createSymbolicLink(link.toPath(), existingDir.toPath()); + File level2 = new File(link, "bar"); + File f = new File(level2, "baz"); + String resolvedPath = getFileUtils().getResolvedPath(f); + assertEquals(new File(new File(existingDir, "bar"), "baz").getAbsolutePath(), resolvedPath); + } + + @Test + public void getResolvedPathWorksForJunctionsThatExist() throws IOException { + assumeTrue("setup doesn't support creation of windows NTFS junctions", + Os.isFamily("windows")); + File existingDir = folder.newFolder(); + File f = new File(folder.getRoot(), "foo"); + createJunction(f, existingDir); + String resolvedPath = getFileUtils().getResolvedPath(f); + assertEquals(existingDir.getAbsolutePath(), resolvedPath); + } + + @Test + public void getResolvedPathDoesntResolveDanglingJunctions() throws IOException { + assumeTrue("setup doesn't support creation of windows NTFS junctions", + Os.isFamily("windows")); + File missingDir = folder.newFolder(); + File f = new File(folder.getRoot(), "bar"); + createJunction(f, missingDir); + assertTrue("failed to delete target directory", missingDir.delete()); + String resolvedPath = getFileUtils().getResolvedPath(f); + assertEquals(f.getAbsolutePath(), resolvedPath); + } + + @Test + public void getResolvedPathWorksForNotExistingFilesInExistingJunctionDirectories() throws IOException { + assumeTrue("setup doesn't support creation of windows NTFS junctions", + Os.isFamily("windows")); + File existingDir = folder.newFolder(); + File link = new File(folder.getRoot(), "foo"); + createJunction(link, existingDir); + File level2 = new File(link, "bar"); + File f = new File(level2, "baz"); + String resolvedPath = getFileUtils().getResolvedPath(f); + assertEquals(new File(new File(existingDir, "bar"), "baz").getAbsolutePath(), resolvedPath); + } + /** * adapt file separators to local conventions */ @@ -905,4 +995,15 @@ public class FileUtilsTest { assertEquals(s1, s2); } } + + private static void createJunction(File junction, File junctionTarget) { + Mklink mklink = new Mklink(); + mklink.setProject(new Project()); + mklink.setLink(junction); + mklink.setTargetFile(junctionTarget); + Mklink.LinkType linkType = new Mklink.LinkType(); + linkType.setValue("junction"); + mklink.setLinkType(linkType); + mklink.execute(); + } }
