Supposing I have a File f that represents a directory, then f.delete() will only delete the directory if it is empty. I've found a couple of examples online that use File.listFiles() or File.list() to get all the files in the directory and then recursively traverses the directory structure and delete all the files. However, since it's possible to create infinitely recursive directory structures (in both Windows and Linux (with symbolic links)) presumably it's possible that programs written in this style might never terminate.
So, is there a better way to write such a program so that it doesn't fall into these pitfalls? Do I need to keep track of everywhere I've traversed and make sure I don't go around in circles or is there a nicer way?
Update: In response to some of the answers (thanks guys!) - I'd rather the code didn't follow symbolic links and stayed within the directory it was supposed to delete. Can I rely on the Commons-IO implementation to do that, even in the Windows case?
If you really want your recursive directory deletion to follow through symbolic links, then I don't think there is any platform independent way of doing so without keeping track of all the directories you have traversed.
However, in pretty much every case I can think of you would just want to delete the actual symbolic link pointing to the directory rather than recursively following through the symbolic link.
If this is the behaviour you want then you can use the FileUtils.deleteDirectory method in Apache Commons IO.
Try Apache Commons IO for a tested implementation.
However, I don't think it this handles the infinite-recursion problem.
File.getCanonicalPath() will tell you the “real” name of the file, including resolved symlinks. When while scanning you come across a directory you alread know (because you stored them in a Map) bail out.
If you could know which files are symlinks, you could just skip over those.
There is unfortunately no "clean" way of detecting symlinks in Java. Check out this pure Java workaround or this one involving native code.
At least under MacOSX, deleting a symbolic link to a directory does not delete the directory itself, and can therefore be deleted even if the target directory is not empty.
I assume this holds for most POSIX operating systems. And as far as I know, links under windows are also just files, and can be deleted as such from a Java program.
Related
If I have 2 Paths for files, both share the same FileStore, how can I verify with Java 17 whether they are pointing to the same file on disk (are hard-linked)? For Unix-like systems there seems at least to be a way to verify whether a file has other hardlinks (get Hard Link Count in Java), but on Windows/NTFS I haven't yet found a way to get either information, except of invoking fsutil hardlink list <file-path> and parsing the output. If necessary, a workaround using JNA would also fine for me.
PS: I have already searched Stackoverflow, but only found similar questions for Python or C#.
I doubt it can be done within Java alone.
Try to use getCanonicalPath() to at least get around relative and absolute path names. But this hardly gets you around symlinks, or symlinks in parent folders.
For such things in Linux systems there is the readlink command. There is lots to be found here on StackOverflow about it: https://stackoverflow.com/search?q=readlink
For Windows you already mentioned fsutil.
Files.isSameFile(path1, path2) checks whether they are hard-linked.
In Java 7 it provides me a way to detect whether a file is symbolic link or not , but why anyone would want to know that .
Files.isSymbolicLink(target) //here target is a path.
I never needed that so far, just wondering what will be the use of it ?
Suppose you're writing a recursive directory copy - you may decide not to follow symbolic links. Or maybe you're creating an archive in a format that doesn't support symbolic links - you may want to warn the user if you encounter one. Or maybe you're writing a diff program, and you want to skip pairs of files which are actually the same file really.
Basically it's a reasonably common property of some files in a file system - why would Java not want to expose that information?
One really good reason you might care about symbolic links is because of security. Sometimes you might want to prevent letting people accessing files from outside a restricted area, so your app checks to make sure the file that is being accessed is not a symbolic link that leads outside of your application's sandbox.
For example, if you're building a network accessible application that runs as a user and that accesses files by path, like maybe a file sharing application, and you want to restrict where people can look for files on your users system, symbolic links could be a security problem.
I used
Files.createTempFile("Hello", "txt");
to create a temporary file and stored the returned Path.
I have an Eclipse IFile resource linked to the temporary file I created:
linkedFile.createLink(tempFile.toUri(), IResource.NONE, null);
If I want to get a Path back from this resource, I call
linkedFile.getLocation().toFile().toPath()
On my local machine, this works 100% fine. But on a remote test machine, I get two different paths:
from Files.createTempFile: C:\Users\USERNA~1\AppData\Local\Temp\Hello3606197456871226795txt
from getLocation().toFile().toPath() C:\Users\Username_Testing\AppData\Local\Temp\Hello3606197456871226795txt
The Folder Username_Testing and only that folder gets turned into a short filename, and only for my direct creation of it as a temporary.
These two paths are not considered equal by Path.equals(...), which is causing a failing of my tests on the remote machine.
In general, this makes me a bit nervous using Path.equals(...) even though in the actual real operation of the application I haven't had any issues yet. Is there a way I can force the system to always use long filenames? Is there something I'm missing that I should be aware of when I do path equality checks, or when converting paths from one form to another?
Update #1: This specific issue is caused by %TEMP% on the target windows machine returning a path using a short filename, that doesn't happen on my local machine. Only test code creates temporary files and folders so this won't affect the real application. The obvious solution to my current problem is fix %TEMP% so the tests run fine in both places, but this solution is not viable in the general sense. It would be nice if there was a way to rectify the situation without modifying the target computer or jumping into native or windows specific code, since I used no such code directly to get both paths.
I found a good, portable solution to my problem, no need to use any platform-specific code. The answer is actually quite simple:
Path.toRealPath()
used something like this:
Path correctedTempFile = tempFile.toRealPath()
Essentially, it is now using the toRealPath() version, which thankfully removes the short filenames, for comparisons against other Paths taken from Eclipse resources. I believe the Eclipse implementation is using only long paths for consistency, so I in turn will use toRealPath to get rid of any potential paths that may use short filenames
This question might help:
Is there a way to generate the 8.3 or 'short' (Windows) version of a file name in Java?
You can get the short path and compare the generated path against both so you know which one to use.
Using JDK 7 I've had success in watching specific directories for new file creations, deletions and modifications using java.nio.file.StandardWatchEventKinds.*
I'm hoping someone may know a way to get Java to detect new file creations regardless of their path.
I am wanting to do this so I can calculate an MD5 sum for each newly written file.
Thanks for any advice you can offer.
Ok, short answer is I don't think Java can do that out of the box. You'd have to either intercept calls to the operating system which would require something closer to the bare metal, or you could do as suggested in another answer and register listeners to every folder from the root down, not to mention other drives in the case of windows machines.
The first approach would need custom JNI which assumes the OS has such a hook and allows user code access.
The second approach would work but could consume a large amount of memory to track all the listeners. In windows right-click on c:\ and select and see just how many folders we're talking about.
One possibility - not a convenient one, but a possibility - is to walk the directory tree for the directories you want to watch, registering each in a WatchService. That's not a very nice way to go about it, and it could be a problem depending on how large the actual directory tree is.
I do not know StandardWatchEvents (although it sounds convenient).
One way to do one you want is to use a native window API such as ReadDirectoryChangesW (or volume changes). It's painful, but works (been there, done that, wish I had another option at the time).
Disregarding my last post, I've found the source of the problem. I'm using
a.renameTo(b)
when b doesn't exist. The reason it doesn't exist is because there is a symbolic link so if b is /usr/name/folder/file, then b really is /mnt/MountTest because the symlink is to that directory.
So the question is, is there an alternative way to rename a file in Java using a string value?
If not, how can this rename procedure be done differently?
A rename would rename it... if it were on the same filesystem.
If a renameTo() fails, you'll need to copy it to the new location, then delete the original.
Renaming files is also highly problematic accross file systems. See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4073756. Commenters of the bug report posted some sample code and also pointed out that you can use Process.exec. Both Apache Commons IO and and Google Guava have utilities for safely moving files as well:
http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/io/Files.html#move(java.io.File,%20java.io.File)
https://commons.apache.org/proper/commons-io/javadocs/api-1.4/org/apache/commons/io/FileUtils.html#moveFile(java.io.File,%20java.io.File)
I think you are confusing things. A java.util.File doesn't represent a file on some filesystem. It represents a path to a file.
The problem is not that a symlink is involved; the problem is that you can't atomically rename across filesystems. The meta-problem is that the Java File operations are badly designed, and don't throw proper exceptions, and provide no error codes when something fails!
How about:
a.renameTo(new File("/your/path/here/");