views:

773

answers:

7

JRE 6, on Windows XP.

Instanciating two File objects with different constructors leads to inconsistent results in the File.exists() method.

Disclaimer : the code below is an abstract, not the actual code. I do not believe this is a File.separator issue at all. I first asked to get early reactions, in case I missed a well understood issue. It now seems that resetting the user.dir system property is one of the causes to this problem. The code below is now reproducible and usable as is. You can copy/paste the Java class and try it, it should behave consistently with what I have listed as results.

Setup:

Create the folder architecture C:\toto\tmp\sub.

Launch the following class from within any folder which does not contain a tmp/sub sub-folder architecture.

Code:

public class TestFileExists {

    public static void main(String[] args) {

        System.setProperty("user.dir", "C:\\toto\\");

        File root = new File("tmp");

        File sub_a = new File(root, "sub");

        File sub_b = new File(root.getAbsolutePath()+"/sub");

        System.out.println("sub_a path ? "+sub_a.getAbsolutePath());
        System.out.println("sub_a exists ? "+sub_a.exists());
        System.out.println("sub_b path ? "+sub_b.getAbsolutePath());
        System.out.println("sub_b exists ? "+sub_b.exists());
        System.out.println("Path equals ? "+ (sub_a.getAbsolutePath().equals(sub_b.getAbsolutePath())));
        System.out.println("Obj equals ? "+ (sub_a.equals(sub_b)));

    }

}

Result :

sub_a path ? C:\toto\tmp\sub
sub_a exists ? false
sub_b path ? C:\toto\tmp\sub
sub_b exists ? true
Path equals ? true
Obj equals ? false

I don't understand the line sub_a exists ? false, and the result is not consistent from machine to machine, nor with the root initial path ant the result is now consistent with from machine to machine.

Now if you reexecute the class by calling java from the command line, from a folder which does contain a tmp/sub sub-folder architecture (like if you call it from D:\, having D:\tmp\sub), you will get the expected :

sub_a path ? C:\toto\tmp\sub
sub_a exists ? true
sub_b path ? C:\toto\tmp\sub
sub_b exists ? true
Path equals ? true
Obj equals ? false

But the existence of sub_a is clearly a false positive, because it checks the existence of another folder than the one described by the getAbsolutePath().

So I strongly suspect that File.exists() depends on the actual Java execution path, and that file existence is not consistent with the absolute path, and exists() uses another path than the "user.dir" system property to check the file system.

Any idea where this problem could come from ?

A: 

On this line:

File sub_b = new File(root.getAbsolutePath()+"/sub");

You should be using the constant File.separator (dependent on the underlying system) rather than a hard-coded forward slash.

Peter
No, the first thing the File constructor does is to normalize the form, so that shouldn't help.
Jonathan Feinberg
Nope, I made a mistake in copying the results. It's actually sub_a which is detected as not existing.
subtenante
A: 

I think it's the directory separator, since Windows usually use backslash "\" while Linux use normal slash "/".

Try changing the line into :

File sub_b = new File(root.getAbsolutePath() + System.getProperty("file.separator") + "sub");
Furuno
Nope, I made a mistake in copying the results. It's actually sub_a which is detected as not existing. Sorry again.
subtenante
A: 

This may be a file separator issue. On Windows, the standard file separator is the backslash (\). When you create sub_b you create a path name (as a String) which contains both slashes and backslashes. The system may get a bit confused on that.

Thomas Pornin
Nope, I made a mistake in copying the results. It's actually sub_a which is detected as not existing. Sorry. Question edited. Sorry again.
subtenante
A: 

These results should be deterministic across machines. Let me break this down by line:

System.out.println("sub_a exists ? "+sub_a.exists());

Here, you're asking if this file actually exists in the filesystem. This should always return the same, assuming the file exists.

System.out.println("sub_b exists ? "+sub_b.exists());

Same thing. You're checking to see if this file actually exists.

System.out.println("Path equals ? "+ (sub_a.getAbsolutePath().equals(sub_b.getAbsolutePath())));

Here you're seeing if the AbsolutePath is the same.

System.out.println("Obj equals ? "+ (sub_a.equals(sub_b)));

And here, you're doing an object comparison with .equals() which under the hood will call the FileSystem class to do a compare() of the two object's path objects, not their AbsolutePath.

The likelyhood is that the file separator is wrong. Try substituting File.separator in the construction of sub_b, like this:

File sub_b = new File(root.getAbsolutePath()+File.separator+"sub");
Kylar
+1  A: 

File can represents an abstract path. Creating a File for tmp and one from the absolute path of tmp will not be equal File objects though their absolute paths are equal.

I'm not exactly sure of a scenario where sub_a won't exist but sub_b will but i doubt it's a separator issue. I suspect it's something to do with this statement from the javadoc for File(File,String):

each pathname string is converted into an abstract pathname and the child abstract pathname is resolved against the parent.

I don't know a situation on Unix-based file systems where, from within /full/path/to, ./tmp/sub will exist but /full/path/to/tmp will not.

If the problem is consistent between systems, it can be understood more clearly by dumping more of each File object's state instead of just printing comparisons.

nicerobot
I agree with you on the not-separator track. It seems to happen when the `user.dir` property is set. I'll update the question as soon as I get more thorough understanding on the context of execution.
subtenante
+4  A: 

Setting user.dir is unsupported. It should be considered a read-only property.

For example the evaluation of Bug 4117557 in the Sun Bug Parade contains this text:

"user.dir", which is initialized during jvm startup, should be used as an informative/readonly system property, try to customize it via command line -Duser.dir=xyz will end up at implementation dependend/unspecified behavior.

While this text is about setting it on the command line, setting it via setProperty() is most likely equally undefined.

When you can reproduce the problem without setting user.dir manually, then you've found a genuine problem.

Joachim Sauer
Perfect, thanks a lot.
subtenante
The reason why this does not work is not because setting user.dir is unsupported. It is because user.dir is ignored when you use relative paths for files, while File.getAbsolutePath() takes it into account. This means that the same file can refer to two locations, depending on whether you convert it to absolute file or not. Solution - avoid using relative paths.
Roman Zenka
@Roman: Sorry, but you're wrong. That is only an effect of the fact that the system assumes that `user.dir` is never changed by the user. `getAbsolutePath()` is documented to "resolved in a system-dependent way" against the current user directory. So `getAbsolutePath()` should *always* return the exact same path that would be accessed if you just used the relative path directly to access a file. That's the entire point of that method!
Joachim Sauer
Joachim: Sorry, my misunderstanding. I thought that "Setting user.dir is unsupported" means that System.setProperty("user.dir", "...") is unsupported. However, no matter how the property is set, it leads to trouble.
Roman Zenka
+2  A: 

Add the following lines to your test:

System.out.println("sub_a = " + sub_a);
System.out.println("sub_b = " + sub_b);

Conclusions:

1) sub_a is a relative path and sub_b is an absolute one.

2) exists() does not use the absolute path

3) setting the environment variable user.dir does not change the current user directory, used by exists()

4) getAbsolutePath uses the user.dir variable instead of the real current user directory! At least for Windows (see Win32FileSystem.getuserPath). That's the problem! (Bug?)

Carlos Heuberger
Absolutely. It seems though that it is not a bug, but a misusage of the user.dir, which was not conceived to be modifiable during execution.
subtenante