Files
JetBrainsRuntime/src/java.base/share/classes/java/io/IoOverNioFileSystem.java
Vladimir Lagunov d6f4568d84 JBR-8965 java.io over nio: improve the performance of IoOverNioFileSystem.getBooleanAttributes
The new code avoids creating unnecessary exceptions.
2025-08-04 16:19:13 +02:00

1235 lines
49 KiB
Java

/*
* Copyright 2025 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.io;
import com.jetbrains.internal.IoOverNio;
import com.jetbrains.internal.IoToNioErrorMessageHolder;
import jdk.internal.misc.VM;
import sun.nio.ch.FileChannelImpl;
import java.nio.channels.FileChannel;
import java.nio.file.AccessDeniedException;
import java.nio.file.AccessMode;
import java.nio.file.DirectoryStream;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileStore;
import java.nio.file.FileSystemException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.DosFileAttributeView;
import java.nio.file.attribute.DosFileAttributes;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import static com.jetbrains.internal.IoOverNio.DEBUG;
class IoOverNioFileSystem extends FileSystem {
/**
* The filesystem created by default in the original OpenJDK.
*/
private final FileSystem parent;
IoOverNioFileSystem(FileSystem parent) {
this.parent = parent;
}
/**
* If this method returns a non-null {@link java.nio.file.FileSystem},
* then {@code java.io} primitives must use this filesystem for all operations inside wherever it is possible.
* Otherwise, {@link java.io} must use legacy functions for accessing the filesystem.
* It is allowed to not use NIO instead of the original java.io code
* if some function is not implemented in NIO.
*/
static java.nio.file.FileSystem acquireNioFs(String path) {
if (!VM.isBooted()) {
return null;
}
if (!IoOverNio.IS_ENABLED_IN_GENERAL) {
return null;
}
if (!IoOverNio.isAllowedInThisThread()) {
return null;
}
java.nio.file.FileSystem nioFs = FileSystems.getDefault();
if (nioFs.getSeparator().equals("\\") && Objects.equals(path, "NUL:")) {
return null;
}
return nioFs;
}
/**
* To be used in {@link java.io.FileInputStream} and so on.
* Constructors of these classes may throw only {@link FileNotFoundException} whatever error actually happens.
*/
static FileNotFoundException convertNioToIoExceptionInStreams(IOException source) {
if (source instanceof FileNotFoundException nsfe) {
return nsfe;
}
String message = IoToNioErrorMessageHolder.removeMessage(source);
if (source instanceof FileSystemException s && s.getFile() != null) {
if (message == null) {
message = s.getFile();
} else {
message = s.getFile() + " (" + message + ")";
}
}
FileNotFoundException result = new FileNotFoundException(message);
result.initCause(source);
return result;
}
private static boolean setPermission0(java.nio.file.FileSystem nioFs, File f, int access, boolean enable, boolean owneronly) {
if (f.getPath().isEmpty()) {
if (nioFs.getSeparator().equals("\\")) {
// This behavior is hardcoded in Java_java_io_WinNTFileSystem_setPermission0,
// and this check happens there before calling GetFileAttributesW.
return (ACCESS_READ == access || ACCESS_EXECUTE == access) && enable;
} else {
// Unlike on Windows,
// Java_java_io_UnixFileSystem_checkAccess0 has no return statements before chmod(),
// and chmod() returns an error for an empty path.
return false;
}
}
Path path;
try {
path = nioFs.getPath(f.getPath());
} catch (InvalidPathException err) {
throw new InternalError(err.getMessage(), err);
}
// See the comment inside getBooleanAttributes that explains the order of file attribute view classes.
BasicFileAttributeView view;
try {
view = Files.getFileAttributeView(path, PosixFileAttributeView.class);
} catch (UnsupportedOperationException ignored) {
view = null;
}
if (view == null) {
try {
view = Files.getFileAttributeView(path, DosFileAttributeView.class);
} catch (UnsupportedOperationException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("File system %s supports neither posix nor dos attributes", nioFs), err)
.printStackTrace(System.err);
}
return false;
}
}
boolean result = true;
if (view instanceof DosFileAttributeView dosView) {
result = false;
try {
// Both the original java.io.File.setReadOnly
// and sun.nio.fs.WindowsFileAttributeViews.Dos.setReadOnly
// use SetFileAttributesW.
//
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileattributesw
// The documentation states:
// > This attribute is not honored on directories.
// However, SetFileAttributesW always returns 0, not changing anything.
// In java.io.File (see Java_java_io_WinNTFileSystem_setReadOnly0), there's an explicit check
// if it is a directory.
// On the contrary, there's no such check in WindowsFileAttributeViews.
if ((access & ACCESS_WRITE) != 0 && !dosView.readAttributes().isDirectory()) {
dosView.setReadOnly(!enable);
result = true;
} else if (access == ACCESS_READ || access == ACCESS_EXECUTE) {
result = enable; // Like in Java_java_io_WinNTFileSystem_setPermission0
}
} catch (IOException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("Can't set read only attributes for %s", f), err)
.printStackTrace(System.err);
}
}
} else if (view instanceof PosixFileAttributeView posixView) {
try {
Set<PosixFilePermission> perms = posixView.readAttributes().permissions();
if ((access & ACCESS_READ) != 0) {
if (enable) {
perms.add(PosixFilePermission.OWNER_READ);
} else {
perms.remove(PosixFilePermission.OWNER_READ);
}
if (!owneronly) {
if (enable) {
perms.add(PosixFilePermission.GROUP_READ);
perms.add(PosixFilePermission.OTHERS_READ);
} else {
perms.remove(PosixFilePermission.GROUP_READ);
perms.remove(PosixFilePermission.OTHERS_READ);
}
}
}
if ((access & ACCESS_WRITE) != 0) {
if (enable) {
perms.add(PosixFilePermission.OWNER_WRITE);
} else {
perms.remove(PosixFilePermission.OWNER_WRITE);
}
if (!owneronly) {
if (enable) {
perms.add(PosixFilePermission.GROUP_WRITE);
perms.add(PosixFilePermission.OTHERS_WRITE);
} else {
perms.remove(PosixFilePermission.GROUP_WRITE);
perms.remove(PosixFilePermission.OTHERS_WRITE);
}
}
}
if ((access & ACCESS_EXECUTE) != 0) {
if (enable) {
perms.add(PosixFilePermission.OWNER_EXECUTE);
} else {
perms.remove(PosixFilePermission.OWNER_EXECUTE);
}
if (!owneronly) {
if (enable) {
perms.add(PosixFilePermission.GROUP_EXECUTE);
perms.add(PosixFilePermission.OTHERS_EXECUTE);
} else {
perms.remove(PosixFilePermission.GROUP_EXECUTE);
perms.remove(PosixFilePermission.OTHERS_EXECUTE);
}
}
}
posixView.setPermissions(perms);
} catch (IOException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("Can't set posix attributes for %s", f), err)
.printStackTrace(System.err);
}
result = false;
}
}
return result;
}
record StreamInitializationBundle(
FileDescriptor fd,
FileChannel channel,
ExternalChannelHolder externalChannelHolder
) {
}
static StreamInitializationBundle initializeStreamUsingNio(
Closeable owner,
java.nio.file.FileSystem nioFs,
File file,
Path nioPath,
Set<OpenOption> optionsForChannel,
NioChannelCleanable channelCleanable) throws FileNotFoundException {
try {
// This tricky thread local variable allows specifying an argument for sun.nio.ch.FileChannelImpl.<init>
// which is not present in the NIO public API and which is not easy to specify another way.
IoOverNio.PARENT_FOR_FILE_CHANNEL_IMPL.set(owner);
return initializeStreamsUsingNio0(owner, nioFs, file, nioPath, optionsForChannel, channelCleanable);
} finally {
IoOverNio.PARENT_FOR_FILE_CHANNEL_IMPL.remove();
}
}
private static StreamInitializationBundle initializeStreamsUsingNio0(
Closeable owner,
java.nio.file.FileSystem nioFs,
File file,
Path nioPath,
Set<OpenOption> optionsForChannel,
NioChannelCleanable channelCleanable) throws FileNotFoundException {
FileChannel channel = null;
try {
channel = nioFs.provider().newFileChannel(nioPath, optionsForChannel);
return new StreamInitializationBundle(
initializeFdUsingNio(owner, channel, channelCleanable),
channel,
new ExternalChannelHolder(owner, channel, channelCleanable)
);
} catch (IOException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("Can't create a %s for %s with %s", owner.getClass().getSimpleName(), file, nioFs), err)
.printStackTrace(System.err);
}
if (channel != null) {
try {
channel.close();
} catch (IOException ignored) {
// Nothing.
}
}
throw IoOverNioFileSystem.convertNioToIoExceptionInStreams(err);
}
}
private static FileDescriptor initializeFdUsingNio(
Closeable owner,
FileChannel channel,
NioChannelCleanable channelCleanable) {
final FileDescriptor fd;
if (channel instanceof FileChannelImpl fci) {
fd = fci.getFD();
fd.attach(owner);
FileCleanable.register(fd);
fci.setUninterruptible();
} else {
channelCleanable.setChannel(channel);
fd = new FileDescriptor();
fd.registerCleanup(new NoOpCleanable(fd));
}
return fd;
}
@Override
public char getSeparator() {
return parent.getSeparator();
}
@Override
public char getPathSeparator() {
return parent.getPathSeparator();
}
@Override
public String normalize(String path) {
// java.nio.file.Path.normalize works differently compared to this normalizer.
// Especially, Path.normalize converts "." into an empty string, which breaks
// even tests in OpenJDK.
return parent.normalize(path);
}
@Override
public int prefixLength(String path) {
// Although it's possible to represent this function via `Path.getRoot`,
// the default file system and java.nio handle corner cases with incorrect paths differently.
return parent.prefixLength(path);
}
@Override
public String resolve(String parent, String child) {
// java.nio is stricter to various invalid symbols than java.io.
return this.parent.resolve(parent, child);
}
@Override
public String getDefaultParent() {
@SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(null);
if (nioFs != null) {
return nioFs.getSeparator();
}
return parent.getDefaultParent();
}
@Override
public String fromURIPath(String path) {
return parent.fromURIPath(path);
}
@Override
public boolean isAbsolute(File f) {
boolean result = isAbsolute0(f);
if (DEBUG.writeTraces()) {
System.err.printf("IoOverNioFileSystem.isAbsolute(%s) = %b%n", f, result);
}
return result;
}
private boolean isAbsolute0(File f) {
@SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath());
if (nioFs != null) {
try {
return nioFs.getPath(f.getPath()).isAbsolute();
} catch (InvalidPathException err) {
// Path parsing in java.nio is stricter than in java.io.
// Giving a chance to the original implementation.
if (DEBUG.writeErrors()) {
new Throwable(String.format("Can't check if a path %s is absolute", f), err)
.printStackTrace(System.err);
}
}
}
return parent.isAbsolute(f);
}
@Override
public boolean isInvalid(File f) {
@SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath());
if (nioFs != null) {
if (nioFs.getSeparator().equals("\\") && f.getPath().equals("NUL:")) {
return false;
}
try {
Path ignored = nioFs.getPath(f.getPath());
return false;
} catch (InvalidPathException ignored) {
return true;
}
}
return parent.isInvalid(f);
}
@Override
public String resolve(File f) {
try {
String result = resolve0(f);
if (DEBUG.writeTraces()) {
System.err.printf("IoOverNioFileSystem.resolve(%s) = %s%n", f, result);
}
return result;
} catch (RuntimeException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("IoOverNioFileSystem.resolve(%s) threw an error%n", f), err)
.printStackTrace(System.err);
}
throw err;
}
}
private String resolve0(File f) {
@SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath());
if (nioFs != null) {
String initialPath = f.toString();
if (getSeparator() == '\\' && prefixLength(initialPath) == 2 && initialPath.charAt(0) == '\\') {
// This behavior is covered by `test/jdk/java/io/File/GetAbsolutePath.java`.
// If this condition passed through Path.toAbsolutePath, an additional excessive slash
// might be added at the end, and the behavior would differ from the original java.io.File.getAbsolutePath.
return initialPath;
}
try {
String resolvedPath = nioFs.getPath(f.getPath()).toAbsolutePath().toString();
if (
initialPath.length() > 2
&& getSeparator() == '\\'
&& initialPath.charAt(1) == ':'
&& initialPath.charAt(2) != '\\'
&& initialPath.charAt(0) != System.getProperty("user.dir", "?").charAt(0)
) {
// The source is a tricky drive-relative path,
// and the current working directory is on a different drive.
// For unclear reasons, the resolved path must have two backslashes after the colon.
// This behavior is covered by `test/jdk/java/io/File/GetAbsolutePath.java`.
resolvedPath = resolvedPath.substring(0, 2) + '\\' + resolvedPath.substring(2);
}
return resolvedPath;
} catch (InvalidPathException err) {
// Path parsing in java.nio is stricter than in java.io.
// Giving a chance to the original implementation.
if (DEBUG.writeErrors()) {
new Throwable(String.format("Can't resolve a path %s", f), err)
.printStackTrace(System.err);
}
}
}
return parent.resolve(f);
}
@Override
public String canonicalize(String path) throws IOException {
try {
String result = canonicalize0(path);
if (DEBUG.writeTraces()) {
System.err.printf("IoOverNioFileSystem.canonicalize(%s) = %s%n", path, result);
}
return result;
} catch (RuntimeException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("IoOverNioFileSystem.canonicalize(%s) threw an error%n", path), err)
.printStackTrace(System.err);
}
throw err;
}
}
private String canonicalize0(String path) throws IOException {
@SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(path);
if (nioFs != null) {
try {
// Unlike java.nio.file.Path.toRealPath, File.getCanonicalFile works with non-existent files
// and resolves symlinks for the first existing parent.
Path nioPath = nioFs.getPath(path);
if (!nioPath.isAbsolute()) {
nioPath = Path.of(System.getProperty("user.dir")).resolve(nioPath);
}
boolean isTrickyDriveRelativePath = !nioPath.toString().startsWith(nioPath.getRoot().toString());
Path suffix = nioFs.getPath("");
while (!nioPath.equals(nioPath.getRoot())) {
Path parent = nioPath.getParent();
if (parent == null) {
parent = nioPath.getRoot();
}
String fileNameStr = nioPath.getFileName().toString();
if (!fileNameStr.isEmpty() && !fileNameStr.equals(".")) {
try {
nioPath = nioPath.toRealPath();
break;
} catch (IOException ignored) {
// Nothing.
}
suffix = nioPath.getFileName().resolve(suffix);
}
nioPath = parent;
}
Path result = nioPath.resolve(suffix).normalize();
// It's not clear why it should work like that,
// but this behaviour is checked by the test `GetAbsolutePath.windowsDriveRelative`.
if (isTrickyDriveRelativePath) {
StringBuilder sb = new StringBuilder(result.getRoot().toString());
for (int i = 0; i < result.getNameCount(); i++) {
sb.append(getSeparator());
sb.append(result.getName(i).toString());
}
return sb.toString();
} else {
return result.toString();
}
} catch (InvalidPathException err) {
// Path parsing in java.nio is stricter than in java.io.
// Giving a chance to the original implementation.
if (DEBUG.writeErrors()) {
new Throwable(String.format("Can't canonicalize a path %s", path), err)
.printStackTrace(System.err);
}
}
}
return parent.canonicalize(path);
}
@Override
public boolean hasBooleanAttributes(File f, int attributes) {
try {
boolean result = hasBooleanAttributes0(f, attributes);
if (DEBUG.writeTraces()) {
System.err.printf("IoOverNioFileSystem.hasBooleanAttributes(%s, %s) = %b%n", f, attributes, result);
}
return result;
} catch (RuntimeException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("IoOverNioFileSystem.hasBooleanAttributes(%s, %s) threw an error", f, attributes), err)
.printStackTrace(System.err);
}
throw err;
}
}
@SuppressWarnings("resource")
private boolean hasBooleanAttributes0(File f, int attributes) {
boolean result;
if (acquireNioFs(f.getPath()) != null) {
result = (getBooleanAttributes0(f) & attributes) == attributes;
} else {
result = parent.hasBooleanAttributes(f, attributes);
}
if (DEBUG.writeTraces()) {
System.err.printf("IoOverNioFileSystem.hasBooleanAttributes(%s, %s) = %b%n", f, attributes, result);
}
return result;
}
@Override
public int getBooleanAttributes(File f) {
int result = getBooleanAttributes0(f);
if (DEBUG.writeTraces()) {
System.err.printf("IoOverNioFileSystem.getBooleanAttributes(%s) = %d%n", f, result);
}
return result;
}
private int getBooleanAttributes0(File f) {
@SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath());
if (nioFs != null) {
try {
Path path = nioFs.getPath(f.getPath());
{
boolean isAbsoluteOrDriveRelative;
if (path.isAbsolute()) {
isAbsoluteOrDriveRelative = true;
} else if (getSeparator() == '\\') {
String pathString = path.toString();
isAbsoluteOrDriveRelative = switch (pathString.length()) {
case 0 -> false;
case 1 -> pathString.charAt(0) == '\\';
case 2 -> Character.isLetter(pathString.charAt(0)) && pathString.charAt(1) == ':';
default -> Character.isLetter(pathString.charAt(0)) && pathString.charAt(1) == ':' && pathString.charAt(2) != '\\';
};
} else {
isAbsoluteOrDriveRelative = false;
}
if (!isAbsoluteOrDriveRelative && (path.getFileName() == null || path.getFileName().toString().isEmpty())) {
// The LibC function `stat` returns an error for such calls.
return 0;
}
}
// The order of checking Posix attributes first and DOS attributes next is deliberate.
// WindowsFileSystem supports these attributes: basic, dos, acl, owner, user.
// LinuxFileSystem supports: basic, posix, unix, owner, dos, user.
// MacOSFileSystem and BsdFileSystem support: basic, posix, unix, owner, user.
//
// Notice that Linux FS supports DOS attributes, which is unexpected.
// The DOS adapter implementation from LinuxFileSystem uses `open` for getting attributes.
// It's not only more expensive than `stat`, but also doesn't work with Unix sockets.
//
// Also, notice that Windows FS does not support Posix attributes, which is expected.
// Checking for Posix attributes first prevents from checking DOS attributes on Linux,
// even though Posix permissions aren't used in this method.
BasicFileAttributes attrs = null;
Set<String> supportedFileAttributeViews = path.getFileSystem().supportedFileAttributeViews();
if (supportedFileAttributeViews.contains("posix")) {
try {
attrs = Files.readAttributes(path, PosixFileAttributes.class);
} catch (UnsupportedOperationException | SecurityException ignored) {
// Nothing.
}
}
if (attrs == null && supportedFileAttributeViews.contains("dos")) {
try {
attrs = Files.readAttributes(path, DosFileAttributes.class);
} catch (UnsupportedOperationException | SecurityException ignored) {
// Nothing.
}
}
if (attrs == null) {
attrs = Files.readAttributes(path, BasicFileAttributes.class);
}
return BA_EXISTS
| (attrs.isDirectory() ? BA_DIRECTORY : 0)
| (attrs.isRegularFile() ? BA_REGULAR : 0)
| (attrs instanceof DosFileAttributes dosAttrs && dosAttrs.isHidden() ? BA_HIDDEN : 0)
// Just like in java.io.UnixFileSystem.isHidden
| (attrs instanceof PosixFileAttributes && f.getName().startsWith(".") ? BA_HIDDEN : 0);
} catch (InvalidPathException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("Can't get attributes for a path %s", f), err)
.printStackTrace(System.err);
}
// Path parsing in java.nio is stricter than in java.io.
// Not returning here to give a chance to the original implementation.
} catch (@SuppressWarnings("removal") IOException | AccessControlException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("Can't get attributes for a path %s", f), err)
.printStackTrace(System.err);
}
return getSeparator() == '/' && f.getName().startsWith(".") ? BA_HIDDEN : 0;
}
}
return parent.getBooleanAttributes(f);
}
@Override
public boolean checkAccess(File f, int access) {
boolean result = checkAccess0(f, access);
if (DEBUG.writeTraces()) {
System.err.printf("IoOverNioFileSystem.checkAccess(%s, %d) = %b%n", f, access, result);
}
return result;
}
private boolean checkAccess0(File f, int access) {
@SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath());
if (nioFs != null) {
if (f.getPath().isEmpty()) {
// Both access() in Posix and GetFileAttributesW in Windows treat an empty path
// as an invalid argument and return an error.
return false;
}
try {
Path path = nioFs.getPath(f.getPath());
int i = 0;
AccessMode[] accessMode = new AccessMode[3];
if ((access & ACCESS_READ) != 0) {
accessMode[i++] = AccessMode.READ;
}
if ((access & ACCESS_WRITE) != 0) {
accessMode[i++] = AccessMode.WRITE;
}
if ((access & ACCESS_EXECUTE) != 0) {
accessMode[i++] = AccessMode.EXECUTE;
}
accessMode = Arrays.copyOf(accessMode, i);
nioFs.provider().checkAccess(path, accessMode);
return true;
} catch (@SuppressWarnings("removal") IOException | AccessControlException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("Can't check access for a path %s", f), err)
.printStackTrace(System.err);
}
return false;
} catch (InvalidPathException err) {
throw new InternalError(err.getMessage(), err);
}
}
return parent.checkAccess(f, access);
}
@Override
public boolean setPermission(File f, int access, boolean enable, boolean owneronly) {
boolean result = setPermission0(f, access, enable, owneronly);
if (DEBUG.writeTraces()) {
System.err.printf("IoOverNioFileSystem.setPermission(%s, %d, %b, %b) = %b%n", f, access, enable, owneronly, result);
}
return result;
}
private boolean setPermission0(File f, int access, boolean enable, boolean owneronly) {
java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath());
if (nioFs != null) {
return setPermission0(nioFs, f, access, enable, owneronly);
}
return parent.setPermission(f, access, enable, owneronly);
}
@Override
public long getLastModifiedTime(File f) {
try {
long result = getLastModifiedTime0(f);
if (DEBUG.writeTraces()) {
System.err.printf("IoOverNioFileSystem.getLastModifiedTime(%s) = %d%n", f, result);
}
return result;
} catch (RuntimeException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("IoOverNioFileSystem.getLastModifiedTime(%s) threw an error", f), err)
.printStackTrace(System.err);
}
throw err;
}
}
private long getLastModifiedTime0(File f) {
@SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath());
if (nioFs != null) {
try {
Path path = nioFs.getPath(f.getPath());
nioFs.provider().readAttributes(path, BasicFileAttributes.class).lastModifiedTime();
} catch (IOException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("Can't get last modified time for a path %s", f), err)
.printStackTrace(System.err);
}
return 0;
} catch (InvalidPathException err) {
throw new InternalError(err.getMessage(), err);
}
}
return parent.getLastModifiedTime(f);
}
@Override
public long getLength(File f) {
try {
long result = getLength0(f);
if (DEBUG.writeTraces()) {
System.err.printf("IoOverNioFileSystem.getLength(%s) = %d%n", f, result);
}
return result;
} catch (RuntimeException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("IoOverNioFileSystem.getLength(%s) threw an error%n", f), err)
.printStackTrace(System.err);
}
throw err;
}
}
private long getLength0(File f) {
@SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath());
try {
if (nioFs != null) {
Path path = nioFs.getPath(f.getPath());
return nioFs.provider().readAttributes(path, BasicFileAttributes.class).size();
}
} catch (IOException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("Can't get file length for a path %s", f), err)
.printStackTrace(System.err);
}
return 0;
} catch (InvalidPathException err) {
throw new InternalError(err.getMessage(), err);
}
return parent.getLength(f);
}
@Override
public boolean createFileExclusively(String pathname) throws IOException {
try {
boolean result = createFileExclusively0(pathname);
if (DEBUG.writeTraces()) {
System.err.printf("IoOverNioFileSystem.createFileExclusively(%s) = %b%n", pathname, result);
}
return result;
} catch (IOException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("IoOverNioFileSystem.createFileExclusively(%s) threw an error", pathname), err)
.printStackTrace(System.err);
}
throw err;
}
}
private boolean createFileExclusively0(String pathname) throws IOException {
java.nio.file.FileSystem nioFs = acquireNioFs(pathname);
try {
// In case of an empty pathname, the default file system always throws an exception.
if (pathname != null && !pathname.isEmpty() && nioFs != null) {
Path path = nioFs.getPath(pathname);
nioFs.provider().newByteChannel(
path,
EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)
).close();
return true;
}
} catch (AccessDeniedException err) {
// A special case for Windows. It can happen if it is an already existing directory.
if (Files.exists(nioFs.getPath(pathname))) {
return false;
}
} catch (FileAlreadyExistsException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("Can't exclusively create a file %s", pathname), err)
.printStackTrace(System.err);
}
return false;
} catch (InvalidPathException err) {
throw new IOException(err.getMessage(), err); // The default file system would throw IOException too.
} catch (IOException err) {
String originalMessage = IoToNioErrorMessageHolder.removeMessage(err);
if (originalMessage != null) {
throw new IOException(originalMessage, err);
} else {
throw err;
}
}
return parent.createFileExclusively(pathname);
}
@Override
public boolean delete(File f) {
try {
boolean result = delete0(f, true);
if (DEBUG.writeTraces()) {
System.err.printf("IoOverNioFileSystem.delete(%s) = %b%n", f, result);
}
return result;
} catch (RuntimeException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("IoOverNioFileSystem.delete(%s) threw an error", f), err)
.printStackTrace(System.err);
}
throw err;
}
}
private boolean delete0(File f, boolean mayRepeat) {
@SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath());
if (nioFs != null) {
Path path = null;
try {
path = nioFs.getPath(f.getPath());
nioFs.provider().delete(path);
return true;
} catch (InvalidPathException err) {
throw new InternalError(err.getMessage(), err);
} catch (IOException err) {
if (err instanceof AccessDeniedException && mayRepeat && path != null && nioFs.supportedFileAttributeViews().contains("dos")) {
try {
DosFileAttributeView attrs = nioFs.provider()
.getFileAttributeView(path, DosFileAttributeView.class, LinkOption.NOFOLLOW_LINKS);
if (attrs != null) {
attrs.setReadOnly(false);
return delete0(f, false);
}
} catch (IOException ignored) {
// Nothing.
}
}
if (DEBUG.writeErrors()) {
new Throwable(String.format("Can't delete a path %s", f), err)
.printStackTrace(System.err);
}
return false;
}
}
return parent.delete(f);
}
@Override
public String[] list(File f) {
try {
String[] result = list0(f);
if (DEBUG.writeTraces()) {
System.err.printf("IoOverNioFileSystem.list(%s) = %s%n", f, Arrays.toString(result));
}
return result;
} catch (RuntimeException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("IoOverNioFileSystem.list(%s) threw an error", f), err)
.printStackTrace(System.err);
}
throw err;
}
}
private String[] list0(File f) {
@SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath());
if (nioFs != null) {
String pathStr = f.getPath();
if (pathStr.isEmpty()) {
// Java_java_io_UnixFileSystem_list0 calls opendir(), and it returns an error for an empty path.
// Java_java_io_WinNTFileSystem_list0 returns null
// after calling fileToNTPath and before any WinAPI call.
return null;
}
try {
if (getSeparator() == '\\') {
// Java_java_io_WinNTFileSystem_list0 deliberately and explicitly removes trailing spaces from the path.
// It doesn't happen in Java_java_io_UnixFileSystem_list0
int i = pathStr.length();
while (i > 0 && pathStr.charAt(i - 1) == ' ') {
i--;
}
pathStr = pathStr.substring(0, i);
}
Path path = nioFs.getPath(pathStr);
try (DirectoryStream<Path> children = nioFs.provider().newDirectoryStream(path, AcceptAllFilter.FILTER)) {
List<String> result = new ArrayList<>();
for (Path child : children) {
result.add(child.getFileName().toString());
}
return result.toArray(String[]::new);
}
} catch (InvalidPathException err) {
throw new InternalError(err.getMessage(), err);
} catch (IOException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("Can't list a path %s", f), err)
.printStackTrace(System.err);
}
return null;
}
}
return parent.list(f);
}
@Override
public boolean createDirectory(File f) {
try {
boolean result = createDirectory0(f);
if (DEBUG.writeTraces()) {
System.err.printf("IoOverNioFileSystem.createDirectory(%s) = %b%n", f, result);
}
return result;
} catch (RuntimeException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("IoOverNioFileSystem.createDirectory(%s) threw an error", f), err)
.printStackTrace(System.err);
}
throw err;
}
}
private boolean createDirectory0(File f) {
@SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath());
if (nioFs != null) {
try {
Path path = nioFs.getPath(f.getPath());
nioFs.provider().createDirectory(path);
return true;
} catch (InvalidPathException err) {
throw new InternalError(err.getMessage(), err);
} catch (IOException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("Can't create a directory %s", f), err)
.printStackTrace(System.err);
}
return false;
}
}
return parent.createDirectory(f);
}
@Override
public boolean rename(File f1, File f2) {
try {
boolean result = rename0(f1, f2);
if (DEBUG.writeTraces()) {
System.err.printf("IoOverNioFileSystem.rename(%s, %s) = %b%n", f1, f2, result);
}
return result;
} catch (RuntimeException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("IoOverNioFileSystem.rename(%s, %s) threw an error", f1, f2), err)
.printStackTrace(System.err);
}
throw err;
}
}
private boolean rename0(File f1, File f2) {
@SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(null);
if (nioFs != null) {
try {
Path path1 = nioFs.getPath(f1.getPath());
Path path2 = nioFs.getPath(f2.getPath());
nioFs.provider().move(path1, path2, StandardCopyOption.REPLACE_EXISTING);
return true;
} catch (InvalidPathException err) {
throw new InternalError(err.getMessage(), err);
} catch (IOException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("Can't rename %s to %s", f1, f2), err)
.printStackTrace(System.err);
}
return false;
}
}
return parent.rename(f1, f2);
}
@Override
public boolean setLastModifiedTime(File f, long time) {
try {
boolean result = setLastModifiedTime0(f, time);
if (DEBUG.writeTraces()) {
System.err.printf("IoOverNioFileSystem.setLastModifiedTime(%s, %d) = %b%n", f, time, result);
}
return result;
} catch (RuntimeException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("IoOverNioFileSystem.setLastModifiedTime(%s, %d) threw an error", f, time), err)
.printStackTrace(System.err);
}
throw err;
}
}
private boolean setLastModifiedTime0(File f, long time) {
@SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath());
if (nioFs != null) {
if (f.getPath().isEmpty()) {
// Here happens the same as in checkAccess0.
return false;
}
try {
Path path = nioFs.getPath(f.getPath());
var view = nioFs.provider().getFileAttributeView(path, BasicFileAttributeView.class);
if (view != null) {
view.setTimes(FileTime.fromMillis(time), null, null);
return true;
} else {
return false;
}
} catch (InvalidPathException err) {
throw new InternalError(err.getMessage(), err);
} catch (IOException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("Can't set last modified time of %s", f), err)
.printStackTrace(System.err);
}
return false;
}
}
return parent.setLastModifiedTime(f, time);
}
@Override
public boolean setReadOnly(File f) {
try {
java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath());
boolean result;
if (nioFs != null) {
result = setPermission0(nioFs, f, ACCESS_WRITE, false, false);
} else {
result = parent.setReadOnly(f);
}
if (DEBUG.writeTraces()) {
System.err.printf("IoOverNioFileSystem.setReadOnly(%s) = %b%n", f, result);
}
return result;
} catch (RuntimeException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("IoOverNioFileSystem.setReadOnly(%s) threw an error", f), err)
.printStackTrace(System.err);
}
throw err;
}
}
@Override
public File[] listRoots() {
try {
File[] result = listRoots0();
if (DEBUG.writeTraces()) {
System.err.printf("IoOverNioFileSystem.listRoots() = %s%n", Arrays.toString(result));
}
return result;
} catch (RuntimeException err) {
if (DEBUG.writeErrors()) {
new Throwable("IoOverNioFileSystem.listRoots() threw an error", err)
.printStackTrace(System.err);
}
throw err;
}
}
private File[] listRoots0() {
@SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(null);
if (nioFs != null) {
List<File> roots = new ArrayList<>();
for (Path rootDirectory : nioFs.getRootDirectories()) {
roots.add(rootDirectory.toFile());
}
return roots.toArray(File[]::new);
}
return parent.listRoots();
}
@Override
public long getSpace(File f, int t) {
try {
long result = getSpace0(f, t);
if (DEBUG.writeTraces()) {
System.err.printf("IoOverNioFileSystem.getSpace(%s, %d) = %d%n", f, t, result);
}
return result;
} catch (RuntimeException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("IoOverNioFileSystem.getSpace(%s, %d) threw an error", f, t), err)
.printStackTrace(System.err);
}
throw err;
}
}
private long getSpace0(File f, int t) {
@SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(f.getPath());
if (nioFs != null) {
try {
Path path = nioFs.getPath(f.getPath());
FileStore store = nioFs.provider().getFileStore(path);
return switch (t) {
case SPACE_TOTAL -> store.getTotalSpace();
case SPACE_USABLE -> store.getUsableSpace();
case SPACE_FREE -> store.getUnallocatedSpace();
default -> throw new IllegalArgumentException("Invalid space type: " + t);
};
} catch (InvalidPathException err) {
throw new InternalError(err.getMessage(), err);
} catch (IOException err) {
if (DEBUG.writeErrors()) {
new Throwable(String.format("Can't get space %s for a path %s", t, f), err)
.printStackTrace(System.err);
}
return 0;
}
}
return parent.getSpace(f, t);
}
@Override
public int getNameMax(String path) {
// Seems impossible with java.nio.
return parent.getNameMax(path);
}
@Override
public int compare(File f1, File f2) {
@SuppressWarnings("resource") java.nio.file.FileSystem nioFs = acquireNioFs(null);
if (nioFs != null) {
try {
Path p1 = nioFs.getPath(f1.getPath());
Path p2 = nioFs.getPath(f2.getPath());
return p1.compareTo(p2);
} catch (InvalidPathException ignored) {
// Path parsing in java.nio is stricter than in java.io.
// Giving a chance to the original implementation.
}
}
return parent.compare(f1, f2);
}
@Override
public int hashCode(File f) {
return parent.hashCode(f);
}
private static class AcceptAllFilter implements DirectoryStream.Filter<Path> {
static final AcceptAllFilter FILTER = new AcceptAllFilter();
private AcceptAllFilter() {
}
@Override
public boolean accept(Path entry) {
return true;
}
}
}