When invoking Files.getFileStore() on a substed drive (on Windows), this results in following error:
The directory is not a subdirectory of the root directory
For example with:
subst P: C:\temp
running:
public static void main(String[] args) throws IOException {
final Path dir = Paths.get("P:/sub");
final FileStore fileStore = Files.getFileStore(dir);
fileStore.isReadOnly();
}
results in:
Exception in thread "main" java.nio.file.FileSystemException: P:\sub: The directory is not a subdirectory of the root directory.
at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:86)
at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97)
at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102)
at sun.nio.fs.WindowsFileStore.create(WindowsFileStore.java:92)
at sun.nio.fs.WindowsFileSystemProvider.getFileStore(WindowsFileSystemProvider.java:482)
at java.nio.file.Files.getFileStore(Files.java:1411)
at utils.FileStoreMain.main(FileStoreMain.java:16)
How to fix this problem and receive the appropriate FileStore for P:?
Have a look at this bug report JDK-8034057 and at a related answer from Alan Bateman.
The problem is that a "substed drive" is not a file store; it just associates a drive letter with a path on an existing drive.
You did:
subst p: c:\temp
which means, in fact, that the real filestore of your p:\sub is the drive associated with c:.
Note: that's just a hypothesis, I don't actually run windows. But if you try and iterate over the filestores (ie, by calling .getFileSystem().getFileStores() on your Path instance) then P: will not appear.
Now, the question remains as to how to obtain the real filestore, if it's possible at all. Maybe a FileAttributeView exists which can provide you with this information; try and see what attribute views are available to you, and their parameters, by using this code:
// using some Path instance named path...
final FileSystem fs = path.getFileSystem();
final Set<String> viewNames = fs.supportedFileAttributesView();
for (final String viewName: viewNames) {
System.out.println("View " + viewName + ':');
System.out.println(Files.readAttributes(path, viewName + ":*"));
}
Maybe there exists a view with the information you are looking for... No guarantee though.
Related
I'm creating a command line application that needs to output some files (more than one) into either a ZIP file or a plain folder depending on the paramters given.
My approach is to encapsulate the target (plain folder/ZIP file) with a FileSystem.
My problem is that I cannot sucessfully create a FileSystem object for a directory other than the current working directory denoting an absolute path on my hard disk:
public class FileSystemWriteTest {
public static void main(String[] args) throws IOException {
Path absolutePath = Paths.get("target", "testpath").toAbsolutePath();
System.out.println(String.format("user.dir before change:\n %s", System.getProperty("user.dir")));
System.setProperty("user.dir", absolutePath.toString());
System.out.println(String.format("changed user.dir:\n %s", System.getProperty("user.dir")));
FileSystem defaultSystem = FileSystems.getDefault();
Path testFilePath = defaultSystem.getPath("test.file");
System.out.println(String.format("expected to be in changed user.dir:\n %s", testFilePath.toAbsolutePath()));
URI uri = absolutePath.toUri();
System.out.println(String.format("URI: %s", uri));
FileSystem localFileSystem =
FileSystems.newFileSystem(uri, Collections.emptyMap());
Path file = localFileSystem.getPath("test.txt");
System.out.println(file.toAbsolutePath());
}
}
The output is:
user.dir before change:
D:\data\scm-workspace\anderes\Test
changed user.dir:
D:\data\scm-workspace\anderes\Test\target\testpath
expected to be in changed user.dir:
D:\data\scm-workspace\anderes\Test\test.file
URI: file:///D:/data/scm-workspace/anderes/Test/target/testpath/
Exception in thread "main" java.lang.IllegalArgumentException: Path component should be '/'
at sun.nio.fs.WindowsFileSystemProvider.checkUri(Unknown Source)
at sun.nio.fs.WindowsFileSystemProvider.newFileSystem(Unknown Source)
at java.nio.file.FileSystems.newFileSystem(Unknown Source)
at java.nio.file.FileSystems.newFileSystem(Unknown Source)
at com.oc.test.filesystem.FileSystemWriteTest.main(FileSystemWriteTest.java:27)
If I change to FileSystems.newFileSystem(Path, Classloader) the Exception changes to:
Exception in thread "main" java.nio.file.ProviderNotFoundException: Provider not found
at java.nio.file.FileSystems.newFileSystem(Unknown Source)
at com.oc.test.filesystem.FileSystemWriteTest.main(FileSystemWriteTest.java:27)
Looks like this only works with regular files, not with directories.
So how can I create a FileSystem object for a directory other than pwd?
There is no built facility for creating a FileSystem with chroot like semantics. The default file system only supports file:/// as URI and doesn’t allow more than one instantiation.
In this regard, FileSystems.getDefault().getPath("test.file") creates a relative path, just like Paths.get("test.file"). The difference between relative paths created for the default file system and other file systems lies in the resolve behavior when no other base path has been specified (e.g. when calling toAbsolutePath() or just trying to open them). But being resolved against the current working directory does not make them a root path.
The best solution for implementing file system agnostic operations, is to let the code receive base Path objects, to resolve relative paths against.
E.g. a simple tree copy routine may look like:
static void copyTree(Path sourceBase, Path targetBase) throws IOException {
try {
Files.walk(sourceBase).forEach(path -> {
if(Files.isRegularFile(path)) try {
Path target = targetBase.resolve(sourceBase.relativize(path).toString());
if(!Files.isDirectory(target.getParent()))
Files.createDirectories(target.getParent());
Files.copy(path, target, StandardCopyOption.COPY_ATTRIBUTES);
} catch(IOException ex) {
throw new UncheckedIOException(ex);
}
});
} catch(UncheckedIOException ex) {
throw ex.getCause();
}
}
For this method, it doesn’t matter whether you’re copying from a harddrive directory to another harddrive directory or to a zip filesystem, or from a zip filesystem to the harddrive, or from a zip file to another zip file, etc.
The most interesting part is the invocation of sourceBase.relativize(path), to get the relative path from the source base path to the sub-path of the actual file. Since relative Path instances still are tied to a particular filesystem, the code invokes toString(), before passing it to targetBase.resolve(…), to ensure that it will work across different filesystems. Note that path.resolve(string) is equivalent to path.resolve(path.getFileSystem().getPath(string)). It would be legitimate, if the method first checks whether both Path instances belong to the same filesystem, to skip the String detour in that case.
I have a simple line of code that should print out first line of a file:
System.out.println(new BufferedReader(new FileReader("file")).readLine());
The class file with this line is located in c:/users/my/project.
A file named "file" exists in c:/users/my/project.
If I open CMD, navigate to c:/users/my/project and run
java MyClass
The first line is printed out and everything is fine.
But if I navigate to C:/ in CMD, and run
java -Duser.dir=c:/users/my/project MyClass
I get a response that file "file" could not be found.
If I move "file" to c:/ and run the same command again then it is found.
As I understand it, changing the user.dir should be equivalent to me being in the folder that it points to but that doesent seem to be true. The class file is found in user.dir, but files are still found in the folder I ran the command in, not the folder that user.dir points to.
Why is this?
If you navigate to another directory and try to run your program with a different user.dir, you also have to set the classpath. The error you get is because it can't find the class, not because it can't find the file.
Try
java -Duser.dir=c:/users/my/project -classpath c:/users/my/project MyClass
Regarding the user.dir property, have a look at the documentation for the File class. It states
A pathname, whether abstract or in string form, may be either absolute or relative. An absolute pathname is complete in that no other information is required in order to locate the file that it denotes. A relative pathname, in contrast, must be interpreted in terms of information taken from some other pathname. By default the classes in the java.io package always resolve relative pathnames against the current user directory. This directory is named by the system property user.dir, and is typically the directory in which the Java virtual machine was invoked.
EDIT
I did a test on my machine and found that file reader looks in the directory that the application was launched from (which may not coincide with the user.dir property). See below:
public static void main(String...args) {
try {
System.out.println("abs path to test.txt : " + new java.io.File("test.txt").getAbsolutePath());
System.out.println("user home : " + System.getProperty("user.home"));
System.out.println("user.dir : " + System.getProperty("user.dir"));
System.out.println("running from : " + new java.io.File(".").getAbsolutePath());
System.out.println(new java.io.BufferedReader(new java.io.FileReader("test.txt")).readLine());
} catch (Exception e) {
System.out.println("Not found");
}
}
Launching this from a directory that doesn't have the file in it I get
C:\Users>java -Duser.home=C:\Users\william -Duser.dir=C:\Users\william -classpath C:\Users\william\Desktop Test
abs path to test.txt : C:\Users\william\test.txt
user home : C:\Users\william
user.dir : C:\Users\william
running from : C:\Users\william\.
Not found
Whereas launching it from a directory that has the file I'm looking for I get
C:\Users\william>java -Duser.home=C:\Users\william -Duser.dir=C:\Users\william -classpath C:\Users\william\Desktop Test
abs path to test.txt : C:\Users\william\test.txt
user home : C:\Users\william
user.dir : C:\Users\william
running from : C:\Users\william\.
hello world
ANOTHER EDIT
I'm using the openjdk 8, so in my case the logic is as follows, from its source:
FileReader constructor with String arg
public FileReader(String fileName) throws FileNotFoundException {
super(new FileInputStream(fileName));
}
FileInputStream constructors
public FileInputStream(String name) throws FileNotFoundException {
this(name != null ? new File(name) : null);
}
public FileInputStream(File file) throws FileNotFoundException {
String name = (file != null ? file.getPath() : null);
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(name);
}
if (name == null) {
throw new NullPointerException();
}
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
fd = new FileDescriptor();
fd.attach(this);
path = name;
open(name);
}
FileInputStream open(String)
private void open(String name) throws FileNotFoundException {
open0(name);
}
The open0(String) method is a native call (see below)
private native void open0(String name) throws FileNotFoundException;
Digging deeper, into the native call in FileInputStream.c
JNIEXPORT void JNICALL
Java_java_io_FileInputStream_open0(JNIEnv *env, jobject this, jstring path) {
fileOpen(env, this, path, fis_fd, O_RDONLY);
}
Therefore the name is what becomes the path that the native fileOpen method will use. This translates to file.getPath(), which is different from file.getAbsolutePath().
file.getPath() with file as new File("test.txt") returns test.txt, which may not be found.
That's the best I can do, I hope it helps.
See this bug report and this question. In short, setting the user.dir via the command line is bad news.
You are getting FileNotFound exception because the program is not able to find the file you are trying to read (In your case fileName is also "file").
The problem is coming because you have defined only the file name not the path. You should either give absolute or relative path for it to make work from any directory. In absence of the path definition the program will try to read from a file in current directory from where your command is executing.
Let's dive into the details:
you have the text file in path : c:/users/my/project/file
You have your Java file in path : c:/users/my/project/MyClass
now when you run inside c:/users/my/project directory , you get the expected output because the text file "file" is defined in this directory only.
when you switch to c: and try to run the program, you get exception because it is trying to find "c:/file" which does not exist.
try to change the line to mention absolute path instead:
System.out.println(new BufferedReader(new FileReader("c:/users/my/project/file")).readLine());
I'd like to use Google's JIMFS for creating a virtual file system for testing purposes. I have trouble getting started, though.
I looked at this tutorial: http://www.hascode.com/2015/03/creating-in-memory-file-systems-with-googles-jimfs/
However, when I create the file system, it actually gets created in the existing file system, i. e. I cannot do:
Files.createDirectory("/virtualfolder");
because I am denied access.
Am I missing something?
Currently, my code looks something like this:
Test Class:
FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
Path vTargetFolder = fs.getPath("/Store/homes/linux/abc/virtual");
TestedClass test = new TestedClass(vTargetFolder.toAbsolutePath().toString());
Java class somewhere:
targetPath = Paths.get(targetName);
Files.createDirectory(targetPath);
// etc., creating files and writing them to the target directory
However, I created a separate class just to test JIMFS and here the creation of the directory doesnt fail, but I cannot create a new file like this:
FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
Path data = fs.getPath("/virtual");
Path dir = Files.createDirectory(data);
Path file = Files.createFile(Paths.get(dir + "/abc.txt")); // throws NoSuchFileException
What am I doing wrong?
The problem is a mix of Default FileSystem and new FileSystem.
Problem 1:
Files.createDirectory("/virtualfolder");
This will actually not compile so I suspect you meant:
Files.createDirectory( Paths.get("/virtualfolder"));
This attempts to create a directory in your root directory of the default filesystem. You need privileges to do that and probably should not do it as a test. I suspect you tried to work around this problem by using strings and run into
Problem 2:
Lets look at your code with comments
FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
// now get path in the new FileSystem
Path data = fs.getPath("/virtual");
// create a directory in the new FileSystem
Path dir = Files.createDirectory(data);
// create a file in the default FileSystem
// with a parent that was never created there
Path file = Files.createFile(Paths.get(dir + "/abc.txt")); // throws NoSuchFileException
Lets look at the last line:
dir + "/abc.txt" >> is the string "/virtual/abc.txt"
Paths.get(dir + "/abc.txt") >> is this as path in the default filesystem
Remember the virtual filesystem lives parallel to the default filesystem.
Paths have a filesystem and can not be used in an other filesystem. They are not just names.
Notes:
Working with virtual filesystems avoid the Paths class. This class will always work in the default filesystem. Files is ok because you have create a path in the correct filesystem first.
if your original plan was to work with a virtual filesystem mounted to the default filesystem you need bit more. I have a project where I create a Webdav server based on a virtual filesystem and then use OS build in methods to mount that as a volume.
In your shell try ls /
the output should contain the "/virtual" directory.
If this is not the case which I suspect it is then:
The program is masking a:
java.nio.file.AccessDeniedException: /virtual/abc.txt
In reality the code should be failing at Path dir = Files.createDirectory(data);
But for some reason this exception is silent and the program continues without creating the directory (or thinking it has) and attempts to write to the directory that doesn't exist
Leaving a misleading java.nio.file.NoSuchFileException
I suggest you use memoryfilesystem instead. It has a much more complete implementation than Jimfs; in particular, it supports POSIX attributes when creating a "Linux" filesystem etc.
Using it, your code will actually work:
try (
final FileSystem fs = MemoryFileSystemBuilder.newLinux()
.build("testfs");
) {
// create a directory, a file within this directory etc
}
Seems like instead of
Path file = Files.createFile(Paths.get(dir + "/abc.txt"));
You should be doing
Path file = Files.createFile(dir.resolve("/abc.txt"))
This way, the context of dir (it's filesystem) is not lost.
While trying to copy some files in my jar file to a temp directory with my java app, the following exception is thrown:
java.nio.file.FileSystemNotFoundException
at com.sun.nio.zipfs.ZipFileSystemProvider.getFileSystem(ZipFileSystemProvider.java:171)
at com.sun.nio.zipfs.ZipFileSystemProvider.getPath(ZipFileSystemProvider.java:157)
at java.nio.file.Paths.get(Unknown Source)
at com.sora.util.walltoggle.pro.WebViewPresentation.setupTempFiles(WebViewPresentation.java:83)
....
and this is a small part of my setupTempFiles(with line numbers):
81. URI uri = getClass().getResource("/webViewPresentation").toURI();
//prints: URI->jar:file:/C:/Users/Tom/Dropbox/WallTogglePro.jar!/webViewPresentation
82. System.out.println("URI->" + uri );
83. Path source = Paths.get(uri);
the webViewPresentation directory resides in the root directory of my jar:
This problem only exits when I package my app as a jar, debugging in Eclipse has no problems. I suspect that this has something to do with this bug but I'm not sure how to correct this problem.
Any helps appreciated
If matters:
I'm on Java 8 build 1.8.0-b132
Windows 7 Ult. x64
A FileSystemNotFoundException means the file system cannot be created automatically; and you have not created it here.
Given your URI, what you should do is split against the !, open the filesystem using the part before it and then get the path from the part after the !:
final Map<String, String> env = new HashMap<>();
final String[] array = uri.toString().split("!");
final FileSystem fs = FileSystems.newFileSystem(URI.create(array[0]), env);
final Path path = fs.getPath(array[1]);
Note that you should .close() your FileSystem once you're done with it.
Accepted answer isn't the best since it doesn't work when you start application in IDE or resource is static and stored in classes!
Better solution was proposed at java.nio.file.FileSystemNotFoundException when getting file from resources folder
InputStream in = getClass().getResourceAsStream("/webViewPresentation");
byte[] data = IOUtils.toByteArray(in);
IOUtils is from Apache commons-io.
But if you are already using Spring and want a text file you can change the second line to
StreamUtils.copyToString(in, Charset.defaultCharset());
StreamUtils.copyToByteArray also exists.
This is maybe a hack, but the following worked for me:
URI uri = getClass().getResource("myresourcefile.txt").toURI();
if("jar".equals(uri.getScheme())){
for (FileSystemProvider provider: FileSystemProvider.installedProviders()) {
if (provider.getScheme().equalsIgnoreCase("jar")) {
try {
provider.getFileSystem(uri);
} catch (FileSystemNotFoundException e) {
// in this case we need to initialize it first:
provider.newFileSystem(uri, Collections.emptyMap());
}
}
}
}
Path source = Paths.get(uri);
This uses the fact that ZipFileSystemProvider internally stores a List of FileSystems that were opened by URI.
If you're using spring framework library, then there is an easy solution for it.
As per requirement we want to read webViewPresentation;
I could solve the same problem with below code:
URI uri = getClass().getResource("/webViewPresentation").toURI();
FileSystems.getDefault().getPath(new UrlResource(uri).toString());
How can I change the current working directory from within a Java program? Everything I've been able to find about the issue claims that you simply can't do it, but I can't believe that that's really the case.
I have a piece of code that opens a file using a hard-coded relative file path from the directory it's normally started in, and I just want to be able to use that code from within a different Java program without having to start it from within a particular directory. It seems like you should just be able to call System.setProperty( "user.dir", "/path/to/dir" ), but as far as I can figure out, calling that line just silently fails and does nothing.
I would understand if Java didn't allow you to do this, if it weren't for the fact that it allows you to get the current working directory, and even allows you to open files using relative file paths....
There is no reliable way to do this in pure Java. Setting the user.dir property via System.setProperty() or java -Duser.dir=... does seem to affect subsequent creations of Files, but not e.g. FileOutputStreams.
The File(String parent, String child) constructor can help if you build up your directory path separately from your file path, allowing easier swapping.
An alternative is to set up a script to run Java from a different directory, or use JNI native code as suggested below.
The relevant OpenJDK bug was closed in 2008 as "will not fix".
If you run your legacy program with ProcessBuilder, you will be able to specify its working directory.
There is a way to do this using the system property "user.dir". The key part to understand is that getAbsoluteFile() must be called (as shown below) or else relative paths will be resolved against the default "user.dir" value.
import java.io.*;
public class FileUtils
{
public static boolean setCurrentDirectory(String directory_name)
{
boolean result = false; // Boolean indicating whether directory was set
File directory; // Desired current working directory
directory = new File(directory_name).getAbsoluteFile();
if (directory.exists() || directory.mkdirs())
{
result = (System.setProperty("user.dir", directory.getAbsolutePath()) != null);
}
return result;
}
public static PrintWriter openOutputFile(String file_name)
{
PrintWriter output = null; // File to open for writing
try
{
output = new PrintWriter(new File(file_name).getAbsoluteFile());
}
catch (Exception exception) {}
return output;
}
public static void main(String[] args) throws Exception
{
FileUtils.openOutputFile("DefaultDirectoryFile.txt");
FileUtils.setCurrentDirectory("NewCurrentDirectory");
FileUtils.openOutputFile("CurrentDirectoryFile.txt");
}
}
It is possible to change the PWD, using JNA/JNI to make calls to libc. The JRuby guys have a handy java library for making POSIX calls called jnr-posix. Here's the maven info
As mentioned you can't change the CWD of the JVM but if you were to launch another process using Runtime.exec() you can use the overloaded method that lets you specify the working directory. This is not really for running your Java program in another directory but for many cases when one needs to launch another program like a Perl script for example, you can specify the working directory of that script while leaving the working dir of the JVM unchanged.
See Runtime.exec javadocs
Specifically,
public Process exec(String[] cmdarray,String[] envp, File dir) throws IOException
where dir is the working directory to run the subprocess in
If I understand correctly, a Java program starts with a copy of the current environment variables. Any changes via System.setProperty(String, String) are modifying the copy, not the original environment variables. Not that this provides a thorough reason as to why Sun chose this behavior, but perhaps it sheds a little light...
The working directory is a operating system feature (set when the process starts).
Why don't you just pass your own System property (-Dsomeprop=/my/path) and use that in your code as the parent of your File:
File f = new File ( System.getProperty("someprop"), myFilename)
The smarter/easier thing to do here is to just change your code so that instead of opening the file assuming that it exists in the current working directory (I assume you are doing something like new File("blah.txt"), just build the path to the file yourself.
Let the user pass in the base directory, read it from a config file, fall back to user.dir if the other properties can't be found, etc. But it's a whole lot easier to improve the logic in your program than it is to change how environment variables work.
I have tried to invoke
String oldDir = System.setProperty("user.dir", currdir.getAbsolutePath());
It seems to work. But
File myFile = new File("localpath.ext");
InputStream openit = new FileInputStream(myFile);
throws a FileNotFoundException though
myFile.getAbsolutePath()
shows the correct path.
I have read this. I think the problem is:
Java knows the current directory with the new setting.
But the file handling is done by the operation system. It does not know the new set current directory, unfortunately.
The solution may be:
File myFile = new File(System.getPropety("user.dir"), "localpath.ext");
It creates a file Object as absolute one with the current directory which is known by the JVM. But that code should be existing in a used class, it needs changing of reused codes.
~~~~JcHartmut
You can use
new File("relative/path").getAbsoluteFile()
after
System.setProperty("user.dir", "/some/directory")
System.setProperty("user.dir", "C:/OtherProject");
File file = new File("data/data.csv").getAbsoluteFile();
System.out.println(file.getPath());
Will print
C:\OtherProject\data\data.csv
You can change the process's actual working directory using JNI or JNA.
With JNI, you can use native functions to set the directory. The POSIX method is chdir(). On Windows, you can use SetCurrentDirectory().
With JNA, you can wrap the native functions in Java binders.
For Windows:
private static interface MyKernel32 extends Library {
public MyKernel32 INSTANCE = (MyKernel32) Native.loadLibrary("Kernel32", MyKernel32.class);
/** BOOL SetCurrentDirectory( LPCTSTR lpPathName ); */
int SetCurrentDirectoryW(char[] pathName);
}
For POSIX systems:
private interface MyCLibrary extends Library {
MyCLibrary INSTANCE = (MyCLibrary) Native.loadLibrary("c", MyCLibrary.class);
/** int chdir(const char *path); */
int chdir( String path );
}
The other possible answer to this question may depend on the reason you are opening the file. Is this a property file or a file that has some configuration related to your application?
If this is the case you may consider trying to load the file through the classpath loader, this way you can load any file Java has access to.
If you run your commands in a shell you can write something like "java -cp" and add any directories you want separated by ":" if java doesnt find something in one directory it will go try and find them in the other directories, that is what I do.
Use FileSystemView
private FileSystemView fileSystemView;
fileSystemView = FileSystemView.getFileSystemView();
currentDirectory = new File(".");
//listing currentDirectory
File[] filesAndDirs = fileSystemView.getFiles(currentDirectory, false);
fileList = new ArrayList<File>();
dirList = new ArrayList<File>();
for (File file : filesAndDirs) {
if (file.isDirectory())
dirList.add(file);
else
fileList.add(file);
}
Collections.sort(dirList);
if (!fileSystemView.isFileSystemRoot(currentDirectory))
dirList.add(0, new File(".."));
Collections.sort(fileList);
//change
currentDirectory = fileSystemView.getParentDirectory(currentDirectory);