From 9477c705c0bd5ce2d445abb5ca44d46656fc315f Mon Sep 17 00:00:00 2001 From: Brian Burkhalter Date: Wed, 26 Feb 2025 16:24:25 +0000 Subject: [PATCH] 8024695: new File("").exists() returns false whereas it is the current working directory Reviewed-by: alanb, rriggs, lancea --- src/java.base/share/classes/java/io/File.java | 3 +- .../share/classes/java/io/FileSystem.java | 18 +- .../unix/classes/java/io/UnixFileSystem.java | 41 +-- .../classes/java/io/WinNTFileSystem.java | 26 +- test/jdk/java/io/File/EmptyPath.java | 267 ++++++++++++++++-- 5 files changed, 308 insertions(+), 47 deletions(-) diff --git a/src/java.base/share/classes/java/io/File.java b/src/java.base/share/classes/java/io/File.java index 7e31379002f..15e687ebf06 100644 --- a/src/java.base/share/classes/java/io/File.java +++ b/src/java.base/share/classes/java/io/File.java @@ -56,7 +56,8 @@ import jdk.internal.util.StaticProperty; * case of Microsoft Windows UNC pathnames, a hostname. Each subsequent name * in an abstract pathname denotes a directory; the last name may denote * either a directory or a file. The empty abstract pathname has no - * prefix and an empty name sequence. + * prefix and an empty name sequence. Accessing a file with the empty abstract + * pathname is equivalent to accessing the current user directory. * *

The conversion of a pathname string to or from an abstract pathname is * inherently system-dependent. When an abstract pathname is converted into a diff --git a/src/java.base/share/classes/java/io/FileSystem.java b/src/java.base/share/classes/java/io/FileSystem.java index db480d931de..3596e0e842a 100644 --- a/src/java.base/share/classes/java/io/FileSystem.java +++ b/src/java.base/share/classes/java/io/FileSystem.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,6 +33,22 @@ import java.lang.annotation.Native; abstract class FileSystem { + /* -- Current Working Directory --*/ + + /* lazy initialization of CWD object */ + private static class CurrentWorkingDirectoryHolder { + static final File CURRENT_WORKING_DIRECTORY = currentWorkingDirectory(); + + private static final File currentWorkingDirectory() { + return new File("."); + } + } + + /* CWD object accessor */ + static File getCWD() { + return CurrentWorkingDirectoryHolder.CURRENT_WORKING_DIRECTORY; + } + /* -- Normalization and construction -- */ /** diff --git a/src/java.base/unix/classes/java/io/UnixFileSystem.java b/src/java.base/unix/classes/java/io/UnixFileSystem.java index ea2ca28fe86..5f9edcd4356 100644 --- a/src/java.base/unix/classes/java/io/UnixFileSystem.java +++ b/src/java.base/unix/classes/java/io/UnixFileSystem.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,11 +29,18 @@ import java.util.Properties; import jdk.internal.util.StaticProperty; final class UnixFileSystem extends FileSystem { - private final char slash; private final char colon; private final String userDir; + private String getPathForSysCalls(String path) { + return path.isEmpty() ? getCWD().getPath() : path; + } + + private File getFileForSysCalls(File file) { + return file.getPath().isEmpty() ? getCWD() : file; + } + UnixFileSystem() { Properties props = System.getProperties(); slash = props.getProperty("file.separator").charAt(0); @@ -154,7 +161,7 @@ final class UnixFileSystem extends FileSystem { @Override public String canonicalize(String path) throws IOException { - return canonicalize0(path); + return canonicalize0(getPathForSysCalls(path)); } private native String canonicalize0(String path) throws IOException; @@ -164,13 +171,13 @@ final class UnixFileSystem extends FileSystem { @Override public int getBooleanAttributes(File f) { - int rv = getBooleanAttributes0(f); + int rv = getBooleanAttributes0(getFileForSysCalls(f)); return rv | isHidden(f); } @Override public boolean hasBooleanAttributes(File f, int attributes) { - int rv = getBooleanAttributes0(f); + int rv = getBooleanAttributes0(getFileForSysCalls(f)); if ((attributes & BA_HIDDEN) != 0) { rv |= isHidden(f); } @@ -183,25 +190,25 @@ final class UnixFileSystem extends FileSystem { @Override public boolean checkAccess(File f, int access) { - return checkAccess0(f, access); + return checkAccess0(getFileForSysCalls(f), access); } private native boolean checkAccess0(File f, int access); @Override public long getLastModifiedTime(File f) { - return getLastModifiedTime0(f); + return getLastModifiedTime0(getFileForSysCalls(f)); } private native long getLastModifiedTime0(File f); @Override public long getLength(File f) { - return getLength0(f); + return getLength0(getFileForSysCalls(f)); } private native long getLength0(File f); @Override public boolean setPermission(File f, int access, boolean enable, boolean owneronly) { - return setPermission0(f, access, enable, owneronly); + return setPermission0(getFileForSysCalls(f), access, enable, owneronly); } private native boolean setPermission0(File f, int access, boolean enable, boolean owneronly); @@ -215,37 +222,37 @@ final class UnixFileSystem extends FileSystem { @Override public boolean delete(File f) { - return delete0(f); + return delete0(getFileForSysCalls(f)); } private native boolean delete0(File f); @Override public String[] list(File f) { - return list0(f); + return list0(getFileForSysCalls(f)); } private native String[] list0(File f); @Override public boolean createDirectory(File f) { - return createDirectory0(f); + return createDirectory0(getFileForSysCalls(f)); } private native boolean createDirectory0(File f); @Override public boolean rename(File f1, File f2) { - return rename0(f1, f2); + return rename0(getFileForSysCalls(f1), getFileForSysCalls(f2)); } private native boolean rename0(File f1, File f2); @Override public boolean setLastModifiedTime(File f, long time) { - return setLastModifiedTime0(f, time); + return setLastModifiedTime0(getFileForSysCalls(f), time); } private native boolean setLastModifiedTime0(File f, long time); @Override public boolean setReadOnly(File f) { - return setReadOnly0(f); + return setReadOnly0(getFileForSysCalls(f)); } private native boolean setReadOnly0(File f); @@ -260,7 +267,7 @@ final class UnixFileSystem extends FileSystem { @Override public long getSpace(File f, int t) { - return getSpace0(f, t); + return getSpace0(getFileForSysCalls(f), t); } private native long getSpace0(File f, int t); @@ -270,7 +277,7 @@ final class UnixFileSystem extends FileSystem { @Override public int getNameMax(String path) { - long nameMax = getNameMax0(path); + long nameMax = getNameMax0(getPathForSysCalls(path)); if (nameMax > Integer.MAX_VALUE) { nameMax = Integer.MAX_VALUE; } diff --git a/src/java.base/windows/classes/java/io/WinNTFileSystem.java b/src/java.base/windows/classes/java/io/WinNTFileSystem.java index dd567be1827..3f383187517 100644 --- a/src/java.base/windows/classes/java/io/WinNTFileSystem.java +++ b/src/java.base/windows/classes/java/io/WinNTFileSystem.java @@ -79,6 +79,14 @@ final class WinNTFileSystem extends FileSystem { return path; } + private String getPathForWin32Calls(String path) { + return (path != null && path.isEmpty()) ? getCWD().getPath() : path; + } + + private File getFileForWin32Calls(File file) { + return file.getPath().isEmpty() ? getCWD() : file; + } + WinNTFileSystem() { Properties props = System.getProperties(); slash = props.getProperty("file.separator").charAt(0); @@ -495,31 +503,31 @@ final class WinNTFileSystem extends FileSystem { @Override public int getBooleanAttributes(File f) { - return getBooleanAttributes0(f); + return getBooleanAttributes0(getFileForWin32Calls(f)); } private native int getBooleanAttributes0(File f); @Override public boolean checkAccess(File f, int access) { - return checkAccess0(f, access); + return checkAccess0(getFileForWin32Calls(f), access); } private native boolean checkAccess0(File f, int access); @Override public long getLastModifiedTime(File f) { - return getLastModifiedTime0(f); + return getLastModifiedTime0(getFileForWin32Calls(f)); } private native long getLastModifiedTime0(File f); @Override public long getLength(File f) { - return getLength0(f); + return getLength0(getFileForWin32Calls(f)); } private native long getLength0(File f); @Override public boolean setPermission(File f, int access, boolean enable, boolean owneronly) { - return setPermission0(f, access, enable, owneronly); + return setPermission0(getFileForWin32Calls(f), access, enable, owneronly); } private native boolean setPermission0(File f, int access, boolean enable, boolean owneronly); @@ -533,7 +541,7 @@ final class WinNTFileSystem extends FileSystem { @Override public String[] list(File f) { - return list0(f); + return list0(getFileForWin32Calls(f)); } private native String[] list0(File f); @@ -545,7 +553,7 @@ final class WinNTFileSystem extends FileSystem { @Override public boolean setLastModifiedTime(File f, long time) { - return setLastModifiedTime0(f, time); + return setLastModifiedTime0(getFileForWin32Calls(f), time); } private native boolean setLastModifiedTime0(File f, long time); @@ -591,7 +599,7 @@ final class WinNTFileSystem extends FileSystem { // that free space <= total space if (t == SPACE_FREE) t = SPACE_USABLE; - return getSpace0(f, t); + return getSpace0(getFileForWin32Calls(f), t); } return 0; } @@ -618,7 +626,7 @@ final class WinNTFileSystem extends FileSystem { } } } - return getNameMax0(s); + return getNameMax0(getPathForWin32Calls(s)); } @Override diff --git a/test/jdk/java/io/File/EmptyPath.java b/test/jdk/java/io/File/EmptyPath.java index 57da428d760..d3211f917dd 100644 --- a/test/jdk/java/io/File/EmptyPath.java +++ b/test/jdk/java/io/File/EmptyPath.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,28 +22,257 @@ */ /* @test - @bug 4842706 - @summary Test some file operations with empty path + * @bug 4842706 8024695 + * @summary Test some file operations with empty path + * @run junit EmptyPath */ -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.FileStore; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import static org.junit.jupiter.api.Assertions.*; public class EmptyPath { - public static void main(String [] args) throws Exception { - File f = new File(""); - f.mkdir(); - try { - f.createNewFile(); - throw new RuntimeException("Expected exception not thrown"); - } catch (IOException ioe) { - // Correct result - } - try { - FileInputStream fis = new FileInputStream(f); - fis.close(); - throw new RuntimeException("Expected exception not thrown"); - } catch (FileNotFoundException fnfe) { - // Correct result + private static final String EMPTY_STRING = ""; + + static File f; + static Path p; + + @BeforeAll + public static void init() { + f = new File(EMPTY_STRING); + p = Path.of(EMPTY_STRING); + } + + @Test + public void canExecute() { + assertTrue(f.canExecute()); + } + + @Test + public void canRead() { + assertTrue(f.canRead()); + } + + @Test + public void canWrite() { + assertTrue(f.canWrite()); + } + + @Test + public void compareTo() { + assertEquals(0, f.compareTo(p.toFile())); + } + + @Test + public void createNewFile() { + assertThrows(IOException.class, () -> f.createNewFile()); + } + + @Test + public void open() throws FileNotFoundException { + assertThrows(FileNotFoundException.class, + () -> new FileInputStream(f)); + } + + @Test + public void delete() { + assertFalse(f.delete()); + } + + @Test + public void equals() { + assertTrue(f.equals(p.toFile())); + } + + @Test + public void exists() { + assertTrue(f.exists()); + } + + @Test + public void getAbsolutePath() { + System.out.println(p.toAbsolutePath().toString() + "\n" + + f.getAbsolutePath()); + assertEquals(p.toAbsolutePath().toString(), f.getAbsolutePath()); + } + + private void checkSpace(long expected, long actual) { + if (expected == 0) { + assertEquals(0L, actual); + } else { + assertTrue(actual > 0); } } + + @Test + public void getFreeSpace() throws IOException { + FileStore fs = Files.getFileStore(f.toPath()); + checkSpace(fs.getUnallocatedSpace(), f.getFreeSpace()); + } + + @Test + public void getName() { + assertEquals(p.getFileName().toString(), f.getName()); + } + + @Test + public void getParent() { + assertNull(f.getParent()); + } + + @Test + public void getPath() { + assertEquals(p.toString(), f.getPath()); + } + + @Test + public void getTotalSpace() throws IOException { + FileStore fs = Files.getFileStore(f.toPath()); + checkSpace(fs.getTotalSpace(), f.getTotalSpace()); + } + + @Test + public void getUsableSpace() throws IOException { + FileStore fs = Files.getFileStore(f.toPath()); + checkSpace(fs.getUsableSpace(), f.getUsableSpace()); + } + + @Test + public void isNotAbsolute() { + assertFalse(f.isAbsolute()); + } + + @Test + public void isAbsolute() { + assertTrue(f.getAbsoluteFile().isAbsolute()); + } + + @Test + public void isDirectory() { + assertTrue(f.isDirectory()); + } + + @Test + public void isFile() { + assertFalse(f.isFile()); + } + + @Test + public void isHidden() { + assertFalse(f.isHidden()); + } + + @Test + public void lastModified() { + assertTrue(f.lastModified() > 0); + } + + @Test + public void length() throws IOException { + assertEquals(Files.size(f.toPath()), f.length()); + } + + @Test + public void list() throws IOException { + String[] files = f.list(); + assertNotNull(files); + Set ioSet = new HashSet(Arrays.asList(files)); + Set nioSet = new HashSet(); + Files.list(p).forEach((x) -> nioSet.add(x.toString())); + assertEquals(nioSet, ioSet); + } + + @Test + public void mkdir() { + assertFalse(f.mkdir()); + } + + @Test + public void setLastModified() { + long t0 = f.lastModified(); + long t = System.currentTimeMillis(); + try { + assertTrue(f.setLastModified(t)); + assertEquals(t, f.lastModified()); + assertTrue(f.setLastModified(t0)); + assertEquals(t0, f.lastModified()); + } finally { + f.setLastModified(t0); + } + } + + // Note: Testing File.setExecutable is omitted because calling + // File.setExecutable(false) makes it impossible to set the CWD to + // executable again which makes subsequent tests fail + + @Test + @DisabledOnOs({OS.WINDOWS}) + public void setReadable() { + assertTrue(f.canRead()); + try { + assertTrue(f.setReadable(false)); + assertFalse(f.canRead()); + assertTrue(f.setReadable(true)); + assertTrue(f.canRead()); + } finally { + f.setReadable(true); + } + } + + @Test + @DisabledOnOs({OS.WINDOWS}) + public void setReadOnly() { + assertTrue(f.canExecute()); + assertTrue(f.canRead()); + assertTrue(f.canWrite()); + try { + assertTrue(f.setReadOnly()); + assertTrue(f.canRead()); + assertFalse(f.canWrite()); + assertTrue(f.setWritable(true, true)); + assertTrue(f.canWrite()); + } finally { + f.setWritable(true, true); + } + } + + @Test + @DisabledOnOs({OS.WINDOWS}) + public void setWritable() { + assertTrue(f.canWrite()); + try { + assertTrue(f.setWritable(false, true)); + assertFalse(f.canWrite()); + assertTrue(f.setWritable(true, true)); + assertTrue(f.canWrite()); + } finally { + f.setWritable(true, true); + } + } + + @Test + public void toPath() { + assertEquals(p, f.toPath()); + } + + @Test + public void toURI() { + assertEquals(f.toPath().toUri(), f.toURI()); + } }