Reliable File.renameTo() alternative on Windows? - java

Java's File.renameTo() is problematic, especially on Windows, it seems.
As the API documentation says,
Many aspects of the behavior of this
method are inherently
platform-dependent: The rename
operation might not be able to move a
file from one filesystem to another,
it might not be atomic, and it might
not succeed if a file with the
destination abstract pathname already
exists. The return value should always
be checked to make sure that the
rename operation was successful.
In my case, as part of an upgrade procedure, I need to move (rename) a directory that may contain gigabytes of data (lots of subdirectories and files of varying sizes). The move is always done within the same partition/drive, so there's no real need to physically move all the files on disk.
There shouldn't be any file locks to the contents of the dir to be moved, but still, quite often, renameTo() fails to do its job and returns false. (I'm just guessing that perhaps some file locks expire somewhat arbitrarily on Windows.)
Currently I have a fallback method that uses copying & deleting, but this sucks because it may take a lot of time, depending on the size of the folder. I'm also considering simply documenting the fact that the user can move the folder manually to avoid waiting for hours, potentially. But the Right Way would obviously be something automatic and quick.
So my question is, do you know an alternative, reliable approach to do a quick move/rename with Java on Windows, either with plain JDK or some external library. Or if you know an easy way to detect and release any file locks for a given folder and all of its contents (possibly thousands of individual files), that would be fine too.
Edit: In this particular case, it seems we got away using just renameTo() by taking a few more things into account; see this answer.

See also the Files.move() method in JDK 7.
An example:
String fileName = "MyFile.txt";
try {
Files.move(new File(fileName).toPath(), new File(fileName).toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING);
} catch (IOException ex) {
Logger.getLogger(SomeClass.class.getName()).log(Level.SEVERE, null, ex);
}

For what it's worth, some further notions:
On Windows, renameTo() seems to fail if the target directory exists, even if it's empty. This surprised me, as I had tried on Linux, where renameTo() succeeded if target existed, as long as it was empty.
(Obviously I shouldn't have assumed this kind of thing works the same across platforms; this is exactly what the Javadoc warns about.)
If you suspect there may be some lingering file locks, waiting a little before the move/rename might help. (In one point in our installer/upgrader we added a "sleep" action and an indeterminate progress bar for some 10 seconds, because there might be a service hanging on to some files). Perhaps even do a simple retry mechanism that tries renameTo(), and then waits for a period (which maybe increases gradually), until the operation succeeds or some timeout is reached.
In my case, most problems seem to have been solved by taking both of the above into account, so we won't need to do a native kernel call, or some such thing, after all.

The original post requested "an alternative, reliable approach to do a quick move/rename with Java on Windows, either with plain JDK or some external library."
Another option not mentioned yet here is v1.3.2 or later of the apache.commons.io library, which includes FileUtils.moveFile().
It throws an IOException instead of returning boolean false upon error.
See also big lep's response in this other thread.

On windows i use Runtime.getRuntime().exec("cmd \\c ") and then use commandline rename function to actually rename files. It is much more flexible, e.g if you want to rename extension of all txt files in a dir to bak just write this to output stream:
rename *.txt *.bak
I know it is not a good solution but apparently it has always worked for me, much better then Java inline support.

In my case it seemed to be a dead object within my own application, which kept a handle to that file. So that solution worked for me:
for (int i = 0; i < 20; i++) {
if (sourceFile.renameTo(backupFile))
break;
System.gc();
Thread.yield();
}
Advantage: it is pretty quick, as there is no Thread.sleep() with a specific hardcoded time.
Disadvantage: that limit of 20 is some hardcoded number. In all my tests, i=1 is enough. But to be sure I left it at 20.

I know this seems a little hacky, but for what I've been needing it for, it seems buffered readers and writers have no issue making the files.
void renameFiles(String oldName, String newName)
{
String sCurrentLine = "";
try
{
BufferedReader br = new BufferedReader(new FileReader(oldName));
BufferedWriter bw = new BufferedWriter(new FileWriter(newName));
while ((sCurrentLine = br.readLine()) != null)
{
bw.write(sCurrentLine);
bw.newLine();
}
br.close();
bw.close();
File org = new File(oldName);
org.delete();
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
}
Works well for small text files as part of a parser, just make sure oldName and newName are full paths to the file locations.
Cheers
Kactus

The following piece of code is NOT an 'alternative' but has reliably worked for me on both Windows and Linux environments:
public static void renameFile(String oldName, String newName) throws IOException {
File srcFile = new File(oldName);
boolean bSucceeded = false;
try {
File destFile = new File(newName);
if (destFile.exists()) {
if (!destFile.delete()) {
throw new IOException(oldName + " was not successfully renamed to " + newName);
}
}
if (!srcFile.renameTo(destFile)) {
throw new IOException(oldName + " was not successfully renamed to " + newName);
} else {
bSucceeded = true;
}
} finally {
if (bSucceeded) {
srcFile.delete();
}
}
}

Why not....
import com.sun.jna.Native;
import com.sun.jna.Library;
public class RenamerByJna {
/* Requires jna.jar to be in your path */
public interface Kernel32 extends Library {
public boolean MoveFileA(String existingFileName, String newFileName);
}
public static void main(String[] args) {
String path = "C:/yourchosenpath/";
String existingFileName = path + "test.txt";
String newFileName = path + "renamed.txt";
Kernel32 kernel32 = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class);
kernel32.MoveFileA(existingFileName, newFileName);
}
}
works on nwindows 7, does nothing if existingFile does not exist, but obviously could be better instrumented to fix this.

I had a similar issue. File was copied rather moving on Windows but worked well on Linux. I fixed the issue by closing the opened fileInputStream before calling renameTo(). Tested on Windows XP.
fis = new FileInputStream(originalFile);
..
..
..
fis.close();// <<<---- Fixed by adding this
originalFile.renameTo(newDesitnationForOriginalFile);

In my case, the error was in the path of the parent directory. Maybe a bug, I had to use the substring to get a correct path.
try {
String n = f.getAbsolutePath();
**n = n.substring(0, n.lastIndexOf("\\"));**
File dest = new File(**n**, newName);
f.renameTo(dest);
} catch (Exception ex) {
...

Well I have found a pretty straight forward solution to this problem -
boolean retVal = targetFile.renameTo(new File("abcd.xyz"));
while(!retVal) {
retVal= targetFile.renameTo(new File("abcd.xyz"));
}
As suggested by Argeman, you can place a counter and limit the number of times the while loop will run so that it doesn't get into an infinite loop in case of some file are being used by another windows process.
int counter = 0;
boolean retVal = targetFile.renameTo(new File("abcd.xyz"));
while(!retVal && counter <= 10) {
retVal = targetFile.renameTo(new File("abcd.xyz"));
counter = counter + 1;
}

I know it sucks, but an alternative is to create a bat script which outputs something simple like "SUCCESS" or "ERROR", invoke it, wait for it to be executed and then check its results.
Runtime.getRuntime().exec("cmd /c start test.bat");
This thread may be interesting. Check also the Process class on how to read the console output of a different process.

You may try robocopy. This is not exactly "renaming", but it's very reliable.
Robocopy is designed for reliable mirroring of directories or directory trees. It has features to ensure all NTFS attributes and properties are copied, and includes additional restart code for network connections subject to disruption.

To move/rename a file you can use this function:
BOOL WINAPI MoveFile(
__in LPCTSTR lpExistingFileName,
__in LPCTSTR lpNewFileName
);
It is defined in kernel32.dll.

File srcFile = new File(origFilename);
File destFile = new File(newFilename);
srcFile.renameTo(destFile);
The above is the simple code. I have tested on windows 7 and works perfectly fine.

Related

Get current path of executed file

I try to write and read to the file in my java project file called Books.txt.
The problem is that I can access the file only if partialPath has full path to the file.
Here is the code:
public <T> List<T> readFromFile(String fileName) {
private String partialPath = "\\HW3\\src\\java\\repos\\";
try {
String path = partialPath + fileName;
FileInputStream fi = new FileInputStream(path);
ObjectInputStream oi = new ObjectInputStream(fi);
// Read objects
List<T> items = (List<T>) oi.readObject();
oi.close();
fi.close();
return items;
} catch (IOException | ClassNotFoundException e) {
}
}
If I set relative path as above I get exception file not found.
My question is how can I set full path to the current directory programmatically?
Here is a code snippet of the Drombler Commons - Client Startup code I wrote, to determine the location of the executable jar. Replace DromblerClientStarter with your main class.
This should work at least when you're running your application as an executable JAR file.
/**
* The jar URI prefix "jar:"
*/
private static final String FULL_JAR_URI_PREFIX = "jar:";
/**
* Length of the jar URI prefix "jar:"
*/
private static final int FULL_JAR_URI_PREFIX_LENGTH = 4;
private Path determineMainJarPath() throws URISyntaxException {
Class<DromblerClientStarter> type = DromblerClientStarter.class;
String jarResourceURIString = type.getResource("/" + type.getName().replace(".", "/") + ".class").toURI().
toString();
int endOfJarPathIndex = jarResourceURIString.indexOf("!/");
String mainJarURIString = endOfJarPathIndex >= 0 ? jarResourceURIString.substring(0, endOfJarPathIndex)
: jarResourceURIString;
if (mainJarURIString.startsWith(FULL_JAR_URI_PREFIX)) {
mainJarURIString = mainJarURIString.substring(FULL_JAR_URI_PREFIX_LENGTH);
}
Path mainJarPath = Paths.get(URI.create(mainJarURIString));
return mainJarPath;
}
Depending on where you bundle Books.txt in your application distribution package, you can use this mainJarPath to determine the path of Books.txt.
I also feel that files created (and later possibly modified and or deleted) by your running Java application is usually better to be placed in a location of the file system that is away from your java application installed home directory. An example might be the 'C:\ProgramData\ApplicationNameFiles\' for the Windows operating system or something similar for other OS platforms. In my opinion, at least for me, I feel it provides less chance of corruption to essential application files due to a poorly maintained drive or, accidental deletion by a User that opens up a File Explorer and decides to take it upon him/her self to clean their system of so called unnecessary files, and other not so obvious reasons.
Because Java can run on almost any platform and such data file locations are platform specific the User should be allowed to select the location to where these files can be created and manipulated from. This location then can be saved as a Property. Indeed, slightly more work but IMHO I feel it may be well worth it.
It is obviously much easier to create a directory (folder) within the install home directory of your JAR file when it's first started and then store and manipulate your application's created data files from there. Definitely much easier to find but then again...that would be a matter of opinion and it wouldn't be mine. Never-the-less if you're bent on doing it this way then your Java application's Install Utility should definitely know where that install path would be, it is therefore just a matter of storing that location somewhere.
No Install Utility? Well then your Java application will definitely need a means to know from where your JAR file is running from and the following code is one way to do that:
public String applicationPath(Class mainStartupClassName) {
try {
String path = mainStartupClassName.getProtectionDomain().getCodeSource().getLocation().getPath();
String pathDecoded = URLDecoder.decode(path, "UTF-8");
pathDecoded = pathDecoded.trim().replace("/", File.separator);
if (pathDecoded.startsWith(File.separator)) {
pathDecoded = pathDecoded.substring(1);
}
return pathDecoded;
}
catch (UnsupportedEncodingException ex) {
Logger.getLogger("applicationPath() Method").log(Level.SEVERE, null, ex);
}
return null;
}
And here is how you would use this method:
String appPath = applicationPath(MyMainStartupClassName.class);
Do keep in mind that if this method is run from within your IDE it will most likely not return the path to your JAR file but instead point to a folder where your classes are stored for the application build.
This is not a unique issue to Java, it's a problem faced by any developer of any language wishing to write data locally to the disk. The are many parts to this problem.
If you want to be able to write to the file (and presumably, read the changes), then you need to devise a solution which allows you find the file in a platform independent way.
Some of the issues
The installation location of the program
While most OS's do have some conventions governing this, this doesn't mean they are always used, for what ever reason.
Also, on some OS's, you are actively restricted from writing to the "installation" location. Windows 8+ doesn't allow you to write to the "Program Files" directory, and in Java, this usually (or at least when I was dealing with it) fails silently.
On MacOS, if you're using a "app bundle", the working directory is automatically set to the user's home directory, making it even more difficult to manage
The execution context (or working directory) may be different from the installation location of the program
A program can be installed in one location, but executed from a different location, this will change the working directory location. Many command line tools suffer from this issue and use different conventions to work around it (ever wonder what the JAVA_HOME environment variable is for 🤔)
Restricted disk access
Many OS's are now actively locking down the locations to which programs can write, even with admin privileges.
A reusable solution...
Most OS's have come up with conventions for solving this issue, not just for Java, but for all developers wishing to work on the platform.
Important Like all guide lines, these are not hard and fast rules, but a recommendations made by the platform authors, which are intended to make your life simpler and make the operation of the platform safer
The most common solution is to simply place the file in a "well known location" on the disk, which can be accessed through an absolute path independently of the installation or execution location of the program.
On Windows, this means placing the file in either ~\AppData\Local\{application name} or ~\AppData\Roaming\{application name}
On MacOS, this means placing the file in ~/Library/Application Data/{application name}
On *nix, this typically means placing the file in ~/.{application name}
It could be argued that you could use ~/.{application name} on all three platforms, but as a user who "shows hidden files", I'd prefer you didn't pollute my home directory.
A possible, reusable, solution...
When Windows 8 came out, I hit the "you can't write to the Program Files" issue, which took some time to diagnose, as it didn't generate an exception, it just failed.
I was also working a lot more on Mac OS as well, so I needed a simple, cross platform solution, so my code could automatically adapt without the need for multiple branches per platform.
To this end, I came with a simple utility class...
public enum SystemUtilities {
INSTANCE;
public boolean isMacOS() {
return getOSName().startsWith("Mac");
}
public boolean isMacOSX() {
return getOSName().startsWith("Mac OS X");
}
public boolean isWindowsOS() {
return getOSName().startsWith("Windows");
}
public boolean isLinux() {
return getOSName().startsWith("Linux");
}
public String getOSName() {
return System.getProperty("os.name");
}
public File getRoamingApplicationSupportPath() {
// For *inx, use '~/.{AppName}'
String path = System.getProperty("user.home");
if (isWindowsOS()) {
path += "\\AppData\\Roaming";
} else if (isMacOS()) {
path += "/Library/Application Support";
}
return new File(path);
}
public File getLocalApplicationSupportPath() {
// For *inx, use '~/.{AppName}'
String path = System.getProperty("user.home");
if (isWindowsOS()) {
path += "\\AppData\\Local";
} else if (isMacOS()) {
path += "/Library/Application Support";
}
return new File(path);
}
}
This provides a baseline from which "independent" code can be built, for example, you could use something like...
File appDataDir = new File(SystemUtilities.INSTANCE.getLocalApplicationSupportPath(), "MyAwesomeApp");
if (appDataDir.exists() || appDataDir.mkdirs()) {
File fileToWrite = new File(appDataDir, "Books.txt");
//...
}
to read/write to the file. Although, personally, I might have manager/factory do this work and return the reference to the end File, but that's me.
What about "pre-packaged" files?
Three possible solutions...
Create the file(s) if they don't exist, populating them with default values as required
Copy "template" file(s) out of the Jar file, if they don't exist
Use an installer to install the files - this is the solution we used when we were faced with changing the location of all our "external" configuration files.
Read only files...
For read only files, the simplest solution is to embedded them within the Jar as "embedded resources", this makes it easier to locate and manage...
URL url = getClass().getResource("/path/to/readOnlyResource.txt");
How you do this, will depend on your build system

How to check that file is opened by another process in Java? [duplicate]

I need to write a custom batch File renamer. I've got the bulk of it done except I can't figure out how to check if a file is already open. I'm just using the java.io.File package and there is a canWrite() method but that doesn't seem to test if the file is in use by another program. Any ideas on how I can make this work?
Using the Apache Commons IO library...
boolean isFileUnlocked = false;
try {
org.apache.commons.io.FileUtils.touch(yourFile);
isFileUnlocked = true;
} catch (IOException e) {
isFileUnlocked = false;
}
if(isFileUnlocked){
// Do stuff you need to do with a file that is NOT locked.
} else {
// Do stuff you need to do with a file that IS locked
}
(The Q&A is about how to deal with Windows "open file" locks ... not how implement this kind of locking portably.)
This whole issue is fraught with portability issues and race conditions:
You could try to use FileLock, but it is not necessarily supported for your OS and/or filesystem.
It appears that on Windows you may be unable to use FileLock if another application has opened the file in a particular way.
Even if you did manage to use FileLock or something else, you've still got the problem that something may come in and open the file between you testing the file and doing the rename.
A simpler though non-portable solution is to just try the rename (or whatever it is you are trying to do) and diagnose the return value and / or any Java exceptions that arise due to opened files.
Notes:
If you use the Files API instead of the File API you will get more information in the event of a failure.
On systems (e.g. Linux) where you are allowed to rename a locked or open file, you won't get any failure result or exceptions. The operation will just succeed. However, on such systems you generally don't need to worry if a file is already open, since the OS doesn't lock files on open.
// TO CHECK WHETHER A FILE IS OPENED
// OR NOT (not for .txt files)
// the file we want to check
String fileName = "C:\\Text.xlsx";
File file = new File(fileName);
// try to rename the file with the same name
File sameFileName = new File(fileName);
if(file.renameTo(sameFileName)){
// if the file is renamed
System.out.println("file is closed");
}else{
// if the file didnt accept the renaming operation
System.out.println("file is opened");
}
On Windows I found the answer https://stackoverflow.com/a/13706972/3014879 using
fileIsLocked = !file.renameTo(file)
most useful, as it avoids false positives when processing write protected (or readonly) files.
org.apache.commons.io.FileUtils.touch(yourFile) doesn't check if your file is open or not. Instead, it changes the timestamp of the file to the current time.
I used IOException and it works just fine:
try
{
String filePath = "C:\sheet.xlsx";
FileWriter fw = new FileWriter(filePath );
}
catch (IOException e)
{
System.out.println("File is open");
}
I don't think you'll ever get a definitive solution for this, the operating system isn't necessarily going to tell you if the file is open or not.
You might get some mileage out of java.nio.channels.FileLock, although the javadoc is loaded with caveats.
Hi I really hope this helps.
I tried all the options before and none really work on Windows. The only think that helped me accomplish this was trying to move the file. Event to the same place under an ATOMIC_MOVE. If the file is being written by another program or Java thread, this definitely will produce an Exception.
try{
Files.move(Paths.get(currentFile.getPath()),
Paths.get(currentFile.getPath()), StandardCopyOption.ATOMIC_MOVE);
// DO YOUR STUFF HERE SINCE IT IS NOT BEING WRITTEN BY ANOTHER PROGRAM
} catch (Exception e){
// DO NOT WRITE THEN SINCE THE FILE IS BEING WRITTEN BY ANOTHER PROGRAM
}
If file is in use FileOutputStream fileOutputStream = new FileOutputStream(file); returns java.io.FileNotFoundException with 'The process cannot access the file because it is being used by another process' in the exception message.

How to test if a file is "complete" (completely written) with Java

Let's say you had an external process writing files to some directory, and you had a separate process periodically trying to read files from this directory. The problem to avoid is reading a file that the other process is currently in the middle of writing out, so it would be incomplete. Currently, the process that reads uses a minimum file age timer check, so it ignores all files unless their last modified date is more than XX seconds old.
I'm wondering if there is a cleaner way to solve this problem. If the filetype is unknown (could be a number of different formats) is there some reliable way to check the file header for the number of bytes that should be in the file, vs the number of bytes currently in the file to confirm they match?
Thanks for any thoughts or ideas!
The way I've done this in the past is that the process writing the file writes to a "temp" file, and then moves the file to the read location when it has finished writing the file.
So the writing process would write to info.txt.tmp. When it's finished, it renames the file to info.txt. The reading process then just had to check for the existence of info.txt - and it knows that if it exists, it has been written completely.
Alternatively you could have the write process write info.txt to a different directory, and then move it to the read directory if you don't like using weird file extensions.
You could use an external marker file. The writing process could create a file XYZ.lock before it starts creating file XYZ, and delete XYZ.lock after XYZ is completed. The reader would then easily know that it can consider a file complete only if the corresponding .lock file is not present.
I had no option of using temp markers etc as the files are being uploaded by clients over keypair SFTP. they can be very large in size.
Its quite hacky but I compare file size before and after sleeping a few seconds.
Its obviously not ideal to lock the thread but in our case it is merely running as a background system processes so seems to work fine
private boolean isCompletelyWritten(File file) throws InterruptedException{
Long fileSizeBefore = file.length();
Thread.sleep(3000);
Long fileSizeAfter = file.length();
System.out.println("comparing file size " + fileSizeBefore + " with " + fileSizeAfter);
if (fileSizeBefore.equals(fileSizeAfter)) {
return true;
}
return false;
}
Note: as mentioned below this might not work on windows. This was used in a Linux environment.
One simple solution I've used in the past for this scenario with Windows is to use boolean File.renameTo(File) and attempt to move the original file to a separate staging folder:
boolean success = potentiallyIncompleteFile.renameTo(stagingAreaFile);
If success is false, then the potentiallyIncompleteFile is still being written to.
This possible to do by using Apache Commons IO maven library FileUtils.copyFile() method. If you try to copy file and get IOException its means that file is not completely saved.
Example:
public static void copyAndDeleteFile(File file, String destinationFile) {
try {
FileUtils.copyFile(file, new File(fileDirectory));
} catch (IOException e) {
e.printStackTrace();
copyAndDeleteFile(file, fileDirectory, delayThreadPeriod);
}
Or periodically check with some delay size of folder that contains this file:
FileUtils.sizeOfDirectory(folder);
Even the number of bytes are equal, the content of the file may be different.
So I think, you have to match the old and the new file byte by byte.
2 options that seems to solve this issue:
the best option- writer process notify reading process somehow that
the writing was finished.
write the file to {id}.tmp, than when finish- rename it to {id}.java, and the reading process run only on *.java files. renaming taking much less time and the chance this 2 process work together decrease.
First, there's Why doesn't OS X lock files like windows does when copying to a Samba share? but that's variation of what you're already doing.
As far as reading arbitrary files and looking for sizes, some files have that information, some do not, but even those that do do not have any common way of representing it. You would need specific information of each format, and manage them each independently.
If you absolutely must act on the file the "instant" it's done, then your writing process would need to send some kind of notification. Otherwise, you're pretty much stuck polling the files, and reading the directory is quite cheap in terms of I/O compared to reading random blocks from random files.
One more method to test that a file is completely written:
private void waitUntilIsReadable(File file) throws InterruptedException {
boolean isReadable = false;
int loopsNumber = 1;
while (!isReadable && loopsNumber <= MAX_NUM_OF_WAITING_60) {
try (InputStream in = new BufferedInputStream(new FileInputStream(file))) {
log.trace("InputStream readable. Available: {}. File: '{}'",
in.available(), file.getAbsolutePath());
isReadable = true;
} catch (Exception e) {
log.trace("InputStream is not readable yet. File: '{}'", file.getAbsolutePath());
loopsNumber++;
TimeUnit.MILLISECONDS.sleep(1000);
}
}
}
Use this for Unix if you are transferring files using FTP or Winscp:
public static void isFileReady(File entry) throws Exception {
long realFileSize = entry.length();
long currentFileSize = 0;
do {
try (FileInputStream fis = new FileInputStream(entry);) {
currentFileSize = 0;
while (fis.available() > 0) {
byte[] b = new byte[1024];
int nResult = fis.read(b);
currentFileSize += nResult;
if (nResult == -1)
break;
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("currentFileSize=" + currentFileSize + ", realFileSize=" + realFileSize);
} while (currentFileSize != realFileSize);
}

Java, Linux: how to detect whether two java.io.Files refer to the same physical file

I'm looking for an efficient way to detect whether two java.io.Files refer to the same physical file. According to the docs, File.equals() should do the job:
Tests this abstract pathname for
equality with the given object.
Returns true if and only if the
argument is not null and is an
abstract pathname that denotes the
same file or directory as this
abstract pathname.
However, given a FAT32 partition (actually a TrueCrypt container) which is mounted at /media/truecrypt1:
new File("/media/truecrypt1/File").equals(new File("/media/truecrypt1/file")) == false
Would you say that this conforms to the specification? And in this case, how to work around that problem?
Update: Thanks to commenters, for Java 7 I've found java.io.Files.isSameFile() which works for me.
The answer in #Joachim's comment is normally correct. The way to determine if two File object refer to the same OS file is to use getCanonicalFile() or getCanonicalPath(). The javadoc says this:
"A canonical pathname is both absolute and unique. [...] Every pathname that denotes an existing file or directory has a unique canonical form."
So the following should work:
File f1 = new File("/media/truecrypt1/File"); // different capitalization ...
File f2 = new File("/media/truecrypt1/file"); // ... but same OS file (on Windows)
if (f1.getCanonicalPath().equals(f2.getCanonicalPath())) {
System.out.println("Files are equal ... no kittens need to die.");
}
However, it would appear that you are viewing a FAT32 file system mounted on UNIX / Linux. AFAIK, Java does not know that this is happening, and is just applying the generic UNIX / Linux rules for file names ... which give the wrong answer in this scenario.
If this is what is really happening, I don't think there is a reliable solution in pure Java 6. However,
You could do some hairy JNI stuff; e.g. get the file descriptor numbers and then in native code, use the fstat(2) system call to get hold of the two files' device and inode numbers and comparing those.
Java 7 java.nio.file.Path.equals(Object) looks like it might give the right answer if you call resolve() on the paths first to resolve symlinks. (It is a little unclear from the javadoc whether each mounted filesystem on Linux will correspond to a distinct FileSystem object.)
The Java 7 tutorials have this section on seeing if two Path objects are for the same file ... which recommends using java.nio.file.Files.isSameFile(Path, Path)
Would you say that this conforms to the specification?
No and yes.
No in the sense that the getCanonicalPath() method is not returning the same value for each existing OS file ... which is what you'd expect from reading the javadoc.
Yes in the technical sense that the Java codebase (not the javadoc) is the ultimate specification ... both in theory and in practice.
you could try to obtain an exclusive write lock on the file, and see if that fails:
boolean isSame;
try {
FileOutputStream file1 = new FileOutputStream (file1);
FileOutputStream file2 = new FileOutputStream (file2);
FileChannel channel1 = file1.getChannel();
FileChannel channel2 = file2.getChannel();
FileLock fileLock1 = channel1.tryLock();
FileLock fileLock2 = channel2.tryLock();
isSame = fileLock2 != null;
} catch(/*appropriate exceptions*/) {
isSame = false;
} finally {
fileLock1.unlock();
fileLock2.unlock();
file1.close();
file2.close();
///cleanup etc...
}
System.out.println(file1 + " and " + file2 + " are " + (isSame?"":"not") + " the same");
This is not always guaranteed to be correct tho - because another process could potentially have obtained the lock, and thus fail for you. But at least this doesn't require you to shell out to an external process.
There's a chance the same file has two paths (e.g. over the network \\localhost\file and \\127.0.0.1\file would refer to the same file with a different path).
I would go with comparing digests of both files to determine whether they are identical or not. Something like this
public static void main(String args[]) {
try {
File f1 = new File("\\\\79.129.94.116\\share\\bots\\triplon_bots.jar");
File f2 = new File("\\\\triplon\\share\\bots\\triplon_bots.jar");
System.out.println(f1.getCanonicalPath().equals(f2.getCanonicalPath()));
System.out.println(computeDigestOfFile(f1).equals(computeDigestOfFile(f2)));
}
catch(Exception e) {
e.printStackTrace();
}
}
private static String computeDigestOfFile(File f) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
InputStream is = new FileInputStream(f);
try {
is = new DigestInputStream(is, md);
byte[] buffer = new byte[1024];
while(is.read(buffer) != -1) {
md.update(buffer);
}
}
finally {
is.close();
}
return new BigInteger(1,md.digest()).toString(16);
}
It outputs
false
true
This approach is of course much slower than any sort of path comparison, it also depends on the size of files. Another possible side effect is that two files will be considered equals equal indifferently from their locations.
The Files.isSameFile method was added for exactly this kind of usage - that is, you want to check if two non-equal paths locate the same file.
On *nix systems, casing does have an importance. file is not the same as File or fiLe.
The API doc of equals() says (right after your quote):
On UNIX systems, alphabetic case is
significant in comparing pathnames; on
Microsoft Windows systems it is not.
You can try Runtime.exec() of
ls -i /fullpath/File # extract the inode number.
df /fullpath/File # extract the "Mounted on" field.
If the mount point and the "inode" number is the same, they are the same file whether you have symbolic links or case-insensitive file systems.
Or even
bash test "file1" -ef "file2"
FILE1 and FILE2 have the same device and inode numbers
The traditional Unix way to test whether two filenames refer to the same underlying filesystem object is to stat them and test whether they have the same [dev,ino] pair.
That does assume no redundant mounts, however. If those are allowed, you have to go about it differently.

How should I load files into my Java application?

How should I load files into my Java application?
The short answer
Use one of these two methods:
Class.getResource(String)
Class.getResourceAsStream(String)
For example:
InputStream inputStream = YourClass.class.getResourceAsStream("image.jpg");
--
The long answer
Typically, one would not want to load files using absolute paths. For example, don’t do this if you can help it:
File file = new File("C:\\Users\\Joe\\image.jpg");
This technique is not recommended for at least two reasons. First, it creates a dependency on a particular operating system, which prevents the application from easily moving to another operating system. One of Java’s main benefits is the ability to run the same bytecode on many different platforms. Using an absolute path like this makes the code much less portable.
Second, depending on the relative location of the file, this technique might create an external dependency and limit the application’s mobility. If the file exists outside the application’s current directory, this creates an external dependency and one would have to be aware of the dependency in order to move the application to another machine (error prone).
Instead, use the getResource() methods in the Class class. This makes the application much more portable. It can be moved to different platforms, machines, or directories and still function correctly.
getResource is fine, but using relative paths will work just as well too, as long as you can control where your working directory is (which you usually can).
Furthermore the platform dependence regarding the separator character can be gotten around using File.separator, File.separatorChar, or System.getProperty("file.separator").
What are you loading the files for - configuration or data (like an input file) or as a resource?
If as a resource, follow the suggestion and example given by Will and Justin
If configuration, then you can use a ResourceBundle or Spring (if your configuration is more complex).
If you need to read a file in order to process the data inside, this code snippet may help BufferedReader file = new BufferedReader(new FileReader(filename)) and then read each line of the file using file.readLine(); Don't forget to close the file.
I haven't had a problem just using Unix-style path separators, even on Windows (though it is good practice to check File.separatorChar).
The technique of using ClassLoader.getResource() is best for read-only resources that are going to be loaded from JAR files. Sometimes, you can programmatically determine the application directory, which is useful for admin-configurable files or server applications. (Of course, user-editable files should be stored somewhere in the System.getProperty("user.home") directory.)
public byte[] loadBinaryFile (String name) {
try {
DataInputStream dis = new DataInputStream(new FileInputStream(name));
byte[] theBytes = new byte[dis.available()];
dis.read(theBytes, 0, dis.available());
dis.close();
return theBytes;
} catch (IOException ex) {
}
return null;
} // ()
use docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ClassLoader.html#getResource(java.lang.String)
public static String loadTextFile(File f) {
try {
BufferedReader r = new BufferedReader(new FileReader(f));
StringWriter w = new StringWriter();
try {
String line = reader.readLine();
while (null != line) {
w.append(line).append("\n");
line = r.readLine();
}
return w.toString();
} finally {
r.close();
w.close();
}
} catch (Exception ex) {
ex.printStackTrace();
return "";
}
}

Categories