In a unit test I am overwriting a config file to test handling bad property values.
I am using Apache Commons IO:
org.apache.commons.io.FileUtils.copyFile(new File(configDir, "xyz.properties.badValue"), new File(configDir, "xyz.properties"), false)
When investigating the file system I can see that xyz.properties is in fact overwritten - size is updated and the content is the same as that of xyz.properties.badValue.
When I complete the test case which goes through code that reads the file into a Properties object (using a FileReader object) I get the properties of the original xyz.properties file, not the newly copied version.
Through debugging where I single step and investigate the file I can rule out it being a timing issue of writing to the file system.
Does the copy step somehow hold a file handle? If so how would I release it again?
If not, does anybody have any idea why this happens and how to resolve it?
Thanks.
If you initialized the FileReader object before this object, then it will have already stored a temp copy of the old version.
You'll need to reset it:
FileReader f = new FileReader("the.file");
// Copy and overwrite "the.file"
f = new FileReader("the.file");
In the Unix filesystem model, the inode containing the file's contents will persist as long as someone has an open filehandle into the file, or there is a directory entry pointing to it.
Replacing the file's name in the directory, does not remove the inode (contents of the file), so your already-open filehandle can continue to be used.
This is actually exploitable to create temporary files that never need to be cleaned up: create the file, then unlink it immediately, while keeping it open. When you close the file handle, the inode is reaped
I realize that this doesn't answer your question directly, but I think that it would be better to maintain two separate files, and arrange for your code to have the name of the configuration file configurable / injected at runtime. That way, your tests can specify which config file to use, rather than overwriting a single file.
Related
This is an issue I have had in many applications.
I want to change the information inside a file, which has an outdated version.
In this instance, I am updating the file that records playlists after adding a song to a playlist. (For reference, I am creating an app for android.)
The problem is if I run this code:
FileOutputStream output = new FileOutputStream(file);
output.write(data.getBytes());
output.close();
And if an IOException occurs while trying to write to the file, the data is lost (since creating an instance of FileOutputStream empties the file). Is there a better method to do this, so if an IOException occurs, the old data remains intact? Or does this error only occur when the file is read-only, so I just need to check for that?
My only "work around" is to inform the user of the error, and give said user the correct data, which the user has to manually update. While this might work for a developer, there is a lot of issues that could occur if this happens. Additionally, in this case, the user doesn't have permission to edit the file themselves, so the "work around" doesn't work at all.
Sorry if someone else has asked this. I couldn't find a result when searching.
Thanks in advance!
One way you could ensure that you do not wipe the file is by creating a new file with a different name first. If writing that file succeeds, you could delete the old file and rename the new one.
There is the possibility that renaming fails. To be completely safe from that, your files could be named according to the time at which they are created. For instance, if your file is named save.dat, you could add the time at which the file was saved (from System.currentTimeMillis()) to the end of the file's name. Then, no matter what happens later (including failure to delete the old file or rename the new one), you can recover the most recent successful save. I have included a sample implementation below which represents the time as a 16-digit zero-padded hexadecimal number appended to the file extension. A file named save.dat will be instead saved as save.dat00000171ed431353 or something similar.
// name includes the file extension (i.e. "save.dat").
static File fileToSave(File directory, String name) {
return new File(directory, name + String.format("%016x", System.currentTimeMillis()));
}
// return the entire array if you need older versions for which deletion failed. This could be useful for attempting to purge any unnecessary older versions for instance.
static File fileToLoad(File directory, String name) {
File[] files = directory.listFiles((dir, n) -> n.startsWith(name));
Arrays.sort(files, Comparator.comparingLong((File file) -> Long.parseLong(file.getName().substring(name.length()), 16)).reversed());
return files[0];
}
Suppose I create a txt file and save it as "Untitled1". I enter eclipse and type the following:
import java.io.*;
public class Test {
public static void main(String[] args){
File f = new File("Untitled1.txt");
boolean isDeleted = f.delete();
System.out.println(isDeleted);
}
}
False was returned from the delete method indicating the file was not deleted. I understand that a file object represents the location of a file and NOT the contents of the file. But then what is actually being deleted? How do you delete a location of a file, without deleting the contents of a file itself?
I also entered the file Path for the Untitled1 file as a parameter to the File objects constructor, that did not delete the Untitled1.txt file either.
A file is identified by its path through the file system, beginning from the root node.
Representation of the path depends on the system. e.g. in windows C:\foo\bar while in linux /home/foo/bar.
So in below code, string path would be converted into abstract pathname and it would create the File instance and when you call the delete method it will try to delete the node. Basically content and path are not really different.
File f = new File("Untitled1.txt");
So, the file itself is deleted. False could be returned for a variety of reasons.
public boolean delete()
Deletes the file or directory denoted by this abstract pathname. If this pathname denotes a directory, then the directory must be empty in order to be deleted.
Note that the Files class defines the delete method to throw an IOException when a file cannot be deleted. This is useful for error reporting and to diagnose why a file cannot be deleted.
Returns:
true if and only if the file or directory is successfully deleted; false otherwise
Throws:
SecurityException - If a security manager exists and its SecurityManager.checkDelete(java.lang.String) method denies delete access to the file
Catch the SecurityException and you will probably find that you are disallowed from directly deleting the file programmatically.
First, please note that File is an old, bad class, that should not be used nowadays.
It's a lot better to use the Files class, more specifically its delete method.
As for what exactly is deleted: a file is a bunch of bytes that sits in a hard disk. The disk - or one of its partitions - is formatted into a filesystem, which is a data structure that organizes directories and files. The particular filesystem dictates how the file is broken up into pieces, how these pieces can be located or added by operations such as seek, read, write etc.
A filesystem has directories, which give you start points for the files. The file path in the hierarchy of directories tells the system where to find the file, including all its information (like pointer to its start, read/write permissions, etc.) The information in the directory that tells where the actual file contents is is sometimes called a "link" (at least in Unix file systems).
When you delete a file, the usual thing that happens is that the particular link to that file from that directory is removed. If that is the last link (the file can be linked from more than one directory, at least in some file systems), the blocks that belong to the file are also marked as free so that they can be allocated to another file.
So your File object tells the system where the file is, but the delete operation ultimately tells the system both to unlink the file from the directory (the penultimate part of the path), and if that's the last link, it also tells the system to go to the file contents and mark it as free.
This is a general description. The exact details and what happens when the content is marked as free is dependent on the particular filesystem used (e.g. ext4,reiserFS... (Linux), HFS+ (MacOS X), NTFS,FAT32... (Windows)).
My app needs to get an existing file for processing. Now I have the path of the file in String format, how can I get the File with it? Is it correct to do this:
File fileToSave = new File(dirOfTheFile);
Here dirOfTheFile is the path of the file. If I implement it in this way, will I get the existing file or the system will create another file for me?
That's what you want to do. If the file exists you'll get it. Otherwise you'll create it. You can check whether the file exists by calling fileToSave.exists() on it and act appropriately if it does not.
The new keyword is creating a File object in code, not necessarily a new file on the device.
I would caution you to not use hardcoded paths if you are for dirOfFile. For example, if you're accessing external storage, call Environment.getExternalStorageDirectory() instead of hardcoding /sdcard.
The File object is just a reference to a file (a wrapper around the path of the file); creating a new File object does not actually create or read the file; to do that, use FileInputStream to read, FileOutputStream to write, or the various File helper methods (like exists(), createNewFile(), etc.) for example to actually perform operations on the path in question. Note that, as others have pointed out, you should use one of the utilities provided by the system to locate directories on the internal or external storage, depending on where you want your files.
try this..
File fileToSave = new File(dirOfTheFile);
if(fileToSave.exists())
{
// the file exists. use it
} else {
// create file here
}
if parent folder is not there you may have to call fileToSave.getParentFile().mkdirs() to create parent folders
when I new a File Object ,I found that there is not a file be create in disk,so I guess a File Obeject is not equal to a disk file, but when I write something to the File object through stream, I found the file be created in disk.
So, can I think like this, new File() - does not create a real file in disk, it is just an object in ram. But when you write something to the File through stream, for example:
FileWrite stream = new FileWrite(file);
stream.write(string);
..the stream will create a new file when the file does not exist (maybe function steam.write() does this?)?
How about File#createNewFile()? If you're using Java 7, you can also use Files.createFile(Path), as in this example from the Java tutorial.
FileWriter creates or truncates the file as required. The write put something in it. File is a file path name which may or may not exist. e.g. File.exists() is not always true and File.delete() can delete a file (i.e. the file no longer exists)
Can I use any utility to do a force rename of a file from Java.io?
I understand that Java 7 has these features, but I can’t use it...
If I do a
File tempFile = File.createTempFile();
tempFile.renameTo(newfile)
and if newfile exists then its fails.
How do I do a force rename?
I think you have to do it manually - that means you have to check if the target-name exists already as a file and remove it before doing the real rename.
You can write a routine, to do it:
public void forceRename(File source, File target) throws IOException
{
if (target.exists()) target.delete();
source.renameTo(target)
}
The downside of this approach is, that after deleting and before renaming another process could create a new file with the name.
Another possibility could be therefore to copy the content of the source into the the target-file and deleting the source-file afterwards. But this would eat up more resources (depending on the size of the file) and should be done only, if the possibility of recreation of the deleted file is likely.
You could always delete newFile first:
File newFile = ...
File file = ...
newFile.delete();
file.renameTo(newFile);
I was unable to rename whenever a folder is open. Setting the following property in Java solved my issue:
dirToRename.setExecutable(true);