I have a file I need to rename to that of an existing file. This is a copy, modify, replace original operation on an existing JAR file. I've got the first two steps done, I just need help with the replace original bit. What's the best way to rename the new version of the JAR to that of the old. The old JAR doesn't need preserving and I don't want to have a copy of the new with its initial name sticking around.
I have commons lang and io already, so if there's a method I've missed, that would be great.
Java.io.File.renameTo(java.io.File)
You might need to call File.delete() first on the original file first - some systems won't rename a file onto an existing file.
You're going to need to create two java.io.File objects: one for the new file, one for the old file.
Lets call these oldFile and newFile.
oldFile.delete()
newFile.renameTo(oldFile);
Edit: mmyers beat me to it.
This should get you reasonably close:
public boolean replaceOldJar(String originalJarPath, java.io.File newJar) {
java.io.File originalJar = new java.io.File(originalJarPath);
if (!originalJar.isFile()) {
return false;
}
boolean deleteOldJarSucceeded = originalJar.delete();
if (!deleteOldJarSucceeded) {
return false;
}
newJar.renameTo(originalJar);
return originalJar.exists();
}
Is there a problem with deleting the old one and renaming the new one?
I'm not completely sure if you are asking simply how to rename the file in the filesystem or if you also want to reload this new version of your jar?
The rename part sounds easy... just use File.renameTo... unfortunately, there are many platform specific problems related to doing this. Some platforms will not allow you to overwrite an existing file, others will not allow you to rename a file so it changes location onto another partition. If you want to make the process completely safe, you need to do the process yourself by removing the old file first and then renaming the new (or copying if a partition change is possible). This is naturally prone to problems if your application/machine crashes while doing this, since it is no longer an atomic operation. You will thus need to add a check to your applications startup process that looks for rename operations that were in the middle when the crash occured. If you are just updating a single file in this way, it should be pretty easy.
However, if you actually want to reload the jar, there are a few more issues to thing about, but you would need to give a bit more detailed view of the situation to get proper advice on how to do it.
Related
My problem is when I print the files within the directory, it prints out stuff like 'thumbs.db' and 'desktop.ini'. How to i make it print the name itself. All the files are .png by the way
static File overlayPath1 = new File(Minecraft.getMinecraft().mcDataDir, "\\TVMod\\" + filesList[0].getName());
thumbs.db and desktop.ini are both files in this directory, but normally in Windows they're hidden. However, because they're still there, they'll show up in your fileList.
If you don't want to use these files, you're going to have to skip them somehow. The implementation I'd suggest is to convert it to an ArrayList, then remove elements that don't match the .png extension.
However, without knowing more about your implementation, though, I can't easily suggest a way to do this.
For example, this snippet throws a NullPointerException(!) on the stream.read() line, assuming the com.google package exists in a JAR somewhere (Guava, for example).
ClassLoader classLoader = getClass().getClassLoader();
URL resource = classLoader.getResource("com/google");
InputStream stream = resource.openStream();
System.out.println(stream.toString()); // Fine -- stream is not null
stream.read(); // NPE inside FilterInputStream.read()!
If com/google is swapped with a package that's in the file system rather than a JAR, then the snippet doesn't crash at all. In fact, it seems to read the files in that directory, separated by newlines, though I can't imagine that behaviour is specified anywhere.
Is there a way test if the resource path "com/google" points to a "normal" resource file or to a directory?
This is a bit of a mess due to some unspecified behaviour for the protocol handlers involved in loading these resources. In this particular situation, there are two: sun.net.www.protocol.file.Handler and sun.net.www.protocol.jar.Handler, and they each handle the directory case a bit differently. Based on some experiments, here's what they each do:
sun.net.www.protocol.file.Handler:
What this Handler does is open a FileURLConnection, which does exactly what you discovered it did when confronted with a directory. You can check if it's a directory just with:
if (resource.getProtocol().equals("file")) {
return new File(resource.getPath()).isDirectory();
}
sun.net.www.protocol.jar.Handler:
This Handler, on the other hand, opens a JarURLConnection which eventually makes its way to a ZipCoder. If you take a look at that code, you'll notice something interesting: jzentry will come back null from the native JNI call because the JAR zip file does not, in fact, contain a file called com/google, and so it returns null to the stream that wraps it.
However, there is a solution. Although the ZipCoder won't find com/google, it will find com/google/ (this is how most ZIP interfaces work, for some reason). In that case, the jzentry will be found, and it'll just return a null byte.
So, cutting through all these random implementation-specific behaviours, you can probably figure out if it's a directory by first trying to access the resource with a trailing / (which is what URLClassLoaders expect for directories anyway). If ClassLoader.getResource() returns non-null, then it's a directory. If it doesn't, try without the trailing slash. If it returns non-null, it's a file. If it still returns null, then it's not even an existing resource.
Kinda hacky, but I don't think there's anything better. I hope this helps!
There is no safe and generic way to detect this. When you use ClassLoader.getResource(), the ClassLoader can return practically anything in the URL, in principle even something you have never seen before if the ClassLoader implements its own URL scheme (and protocol).
Your only option is to analyze the URL returned by getResource(), the protocol should hint at what it is (e.g. "file://"). But beware, depending on environment it may return things you did not plan for.
But to just access a resource, you don't care where it comes from (you may care if you're debugging a configuration issue, but your code should not care).
In general you should not make assumptions about the returned InputStream's capabilities, i.e. do not rely on it supporting mark/reset etc. The only safe operation would be simply reading the Stream. If an IOException occurs during read it indicates a problem with access to the resource (network connection lost etc.).
EDIT: getResource() should IMO only return resources (e.g. files or zip file entries), but never directories (since they are not resources). However I wouldn't count on every possible ClassLoader to do so, and I'm not sure what the correct behavior is (if its even specified somewhere).
I think that there are 2 solutions.
Naive solution based on analysis of the path itself. If it ends with .jar or .zip or .war or .ear it is a file. Otherwise it is a directory. I think that this approach will work in 99.99% of cases unless somebody tries to make you you to fail on purpose. For example by defining soft link that looks like a directory but is a file or vise versa.
Try to mimic the JVM logic that interprets paths of classpath relatively to the current working directory. So, retrieve current working directory by using new File("."), then take classpath, split it and for each its element use new File(".", classPathElement) unless it is defined using absolute path.
Good luck with this.
The problem is that I need the file to move before the rest of my logic will work so when the method returns false I stop execution.
However, when I check on the file in windows explorer it has a new name and it moved.
Just curious why this is happening.
here is some sample code I just tried to recreate the issue. It's pretty much the same thing and it's working fine.
File testfile = new File("TestFile");
if(!testfile.exists()){
testfile.mkdirs();
}
File sample = new File("sample.txt");
if(sample.exists()){
boolean success = sample.renameTo(new File(testfile.getPath() + "\\" + sample.getName()));
if(success){
System.out.println("Moved");
}
else{
System.out.println("Failed");
}
}
Edit: Solved it. I'm sorry for wasting everyone's time with something so silly. However, I really dont think I would have tracked this down if not for making this post.
The solution was that I was actually looping through several files to move. When the output said it failed then the program stopped and when I looked in explorer only the first of the files was actually moved so I assumed it was moving and then returning false. However, the issue was that I was using the wrong variable as an index and so what was happeneing was that it did successfully move the file in index 0 and then when the loop repeated the index didnt increment so it tried to move index 0 again and therefore failed.
Like I said, very stupid but thanks for bearing with me.
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.
You can use apache.commons.io library, which includes FileUtils.moveFile() or also the Files.move() method in JDK 7.
Isn't it possible that you file has a Inputstream open somewhere but has not been closed and so the rename is not working. Try closing all open streams relevant to the file object before closing.
This one worked for me
File file = new File("E:/Javadocs/" , "new.txt");
File file1 = new File("E:/Javadocs/" , "myDoc.txt");
file1.createNewFile();
if (file1.exists()){
System.out.println(file1.renameTo(file));
}
This will create a file myDoc.txt and rename it to new.txt and will print true
I've also tried with File(URI) constructor it worked fine
For those unfamiliar with JNotify, this is a library which provides an easy way to monitor events in a directory.
For instance, when a file gets deleted in the selected folder, the method "fileDeleted" gets called, along with a few parameters. Here's an example of the fileDeleted method:
public void fileDeleted(int wd, String rootPath, String name) {
print("deleted " + rootPath + " : " + name);
}
Now, I would like to know if the deleted file was a file or directory. My usual approach is to create a new File object with the given path, and use the methods isFile() and isDirectory()
However, since this file is already deleted, these methods always return false.
So here's my concrete question: I have the path to a deleted file or directory, how can I tell wether it was a file or a directory? Is there a workaround to this? What's the best practice to do here?
Thank you in advance.
I suggest using a better API for this, like Commons IO. It has this distinction in its interface org.apache.commons.io.monitor.FileAlterationListener and its methods onFile...(), onDirectory...(). Alternatively, and this is probably the best approach, use the new standard feature for this that comes with Java 7, WatchService, as discussed here.
How big is the directory structure you're looking at?
My first instinct is to build an internal representation of the directory structure, using some simple graph traversal algorithm, and then do a lookup every time something is removed to figure out what it was.
<edit>
If you know your directory structure is a strict tree you can use a simple recursion to traverse the file system, and create a map of Files or Strings to boolean, so you can do an easy lookup. Then, once you've got the map built it should be easy to maintain using the JNotify events.
<edit/>
even for medium-sized directories I would think this could be made pretty quick. What is this for? Might there be another way of going about achieving the same goal?
I am facing the same problem. Yet as far as I understand it, Java's WatchService does not allow monitoring of subdirectories, so I cannot use it (task is to monitor changes to a structure containing ~40K folders). I will try and go ahead using the simple (and fallible) heuristic
If it contains a dot ('.'), it's a file.
I will post updates if I come across something more sophisticated...
Why doesn't File.renameTo(...) create sub-directories contained in the destination file path?
For instance,
File source = new File(System.getProperty("user.dir") +
"/src/MyFolder/MyZipFolder.zip");
File dest = new File(System.getProperty("user.dir") +
"/src/MyOtherFolder/MyZipFolder.zip");
System.out.println(source.renameTo(dest));
Since MyOtherFolder does not exist, this will always return false. In order for this to work, I have to ensure that all sub-directories exist either by creating them programmatically(i.e. mkdirs()), or manually. Is there a reason why this functionality was not included in this method?
Why?
Possibly for consistency / compatibility with the APIs that typical operating systems and other programming language runtime libraries provide.
Possibly because it would be a bad idea to create the intermediate directories if the user didn't really mean this to happen; e.g. if he / she simply mistyped one of the directory names in the path.
But it is not really relevant. The bottom line is that this is the way that the renameTo method behaves.
The current File API isn't very well implemented in Java. There is a lot of functionality that would be desirable in a File API that isn't currently present such as move, copy and retrieving file metadata.
I don't think anyone will be able to give you an answer as to why the API is written as is. Probably a poor first draft that went live and couldn't be changed due to backwards compatibility issues.
These issue have been addressed in the upcoming Java 7. A entirely new API has been created to deal with files java.nio.file.Files.
Creating sub-directories may be considered as unexpected side effect from other point of view. Are you sure everyone needs it implicitly?
You have answers but I was thinking along the lines:
A feature request to add a new method File.renameTo(File src, File destination, int makeDirs)
with three constants for makeDirs:
1) do not make sub folder(s)/ dirs
2) only make the final folder if it does not exist meaning if you specify /r1/r2/r3/file.extn then only make r3 if it does not exist, if r2 or any other does not exist then return false.
3) make all possible sub dirs
if its a OS that does not have sub folders then do as you do now
the old method would remain as is