I am having trouble accessing a file within my Jar on Windows. I do not have this problem when I run it on Unix. I have created the jar both on Windows & Unix and it makes no difference. Either way it does not run on Windows.
I ran the jar -tf command on my jar and the class I am running from is located in: a/b/c/d/ClassOne.class. The class I am looking for is located in my base directory of the jar: ClassTwo.class
My code in this ClassOne looks like the following:
String path = File.separator + "myYAML.yml";
InputStream in = MetricCollector.class.getResourceAsStream(path);
InputStreamReader isr = new InputStreamReader(in);
BufferedReader input = new BufferedReader(isr);
My code breaks on the last line shown throwing a NullPointerException which I can only believe means it cannot find the path I have given it. However, this exact code works great on my debugger and on Unix when I run the jar.
I have also tested the following paths:
"myYAML.yml"
File.seperator + ".." + File.seperator + ".." + File.seperator + ".." + File.seperator + ".." + "myYAML.yml"
".." + File.seperator + ".." + File.seperator + ".." + File.seperator + ".." + "myYAML.yml"
all to no avail.
I have used the following Stack Overflow posts to get as far as I can, but they do not seem to have an answer for me: How to reference a resource file correctly for JAR and Debugging?, Accessing resources within a JAR file and Reading file from within JAR not working on windows
Any additional help I would be extremely grateful for. Thanks in advance.
File.separator cannot work on Windows, it returns \. You need to use / as a separator regardless of the OS.
The Class#getResource(String) states
Before delegation, an absolute resource name is constructed from the
given resource name using this algorithm:
If the name begins with a '/' ('\u002f'), then the absolute name of
the resource is the portion of the name following the '/'. Otherwise,
the absolute name is of the following form: modified_package_name/name
Where the modified_package_name is the package name of this object
with '/' substituted for '.' ('\u002e').
In other words, you must use /. This is further explained in the javadoc for ClassLoader.html#getResource(java.lang.String) to which Class#getResource delegates.
The name of a resource is a /-separated path name that identifies the
resource.
If the resource is at the root of the classpath, use
InputStream in = MetricCollector.class.getResourceAsStream("/myYAML.yml");
Related
The getResourceAsStream-method returns null whenever running the executable jar in a directory which ends with a exclamation mark.
For the following example, I have a Eclipse project the following directory structure:
src\ (Source Folder)
main\ (Package)
Main.java
res\ (Source Folder)
images\
Logo.png
I'm reading the Logo.png as follows:
public static void main(String[] args) throws IOException {
try (InputStream is = Main.class.getClassLoader().getResourceAsStream("images/Logo.png")) {
Image image = ImageIO.read(is);
System.out.println(image);
}
}
See the attachment for 2 test cases. First, the executable jar is started from the directory "D:\test123!##" without any problems. Secondly, the executable jar is started from the directory "D:\test123!##!!!", with problems.
Are directories ending with an exclamation mark not supported? Is the code wrong?
Thanks in advance.
Probably because of this bug or any of the many similar bugs in the Java bug database:
http://bugs.sun.com/view_bug.do?bug_id=4523159
The reason is that "!/" in a jar URL is interpreted as the separator between the JAR file name and the path within the JAR itself. If a directory name ends with !, the "!/" character sequence at the end of the directory is incorrectly interpreted. In your case, you are actually trying to access a resource with the following URL:
jar:file:///d:/test1231##!!!/test.jar!/images/Logo.png
The bug has been open for almost 12 years and is not likely to be fixed. Actually I don't know how it can be fixed without breaking other things. The problem is the design decision to use ! as a character with a special meaning (separator) in the URL scheme for JAR files:
jar:<URL for JAR file>!/<path within the JAR file>
Since the exclamation mark is an allowed character in URLs, it may occur both in the URL to the JAR file itself, as well as in the path within the JAR file, making it impossible in some cases to find the actual "!/" separator.
A simple work around for Windows is to use "\" instead of "/" in the path. That would mean the "!/" character sequence is found after the full path. For instance:
new URL("jar:file:\\d:\\test1231##!!!\\test.jar!/images/Logo.png");
My Code:
File jar = new File(jarPath + "/" + jarName);
URL url = new URL("jar:" + jar.toURI() + "!" + dataFilePath);
InputStream stream = null;
try {
stream = url.openStream();
} catch (FileNotFoundException e) {
// Windows fix
URL urlFix = new URL("jar:" + jar.toURI().toString().replace('/', '\\')
+ "!" + dataFilePath);
stream = urlFix.openStream();
}
I use toURI() because it handles things like spaces.
Fixes:
The fix itself would be for Java to check if the file exists and if not continue to the next separator (the "!/" part of the url) until the separators are exhausted, then throw the exception. So it would see that "d:\test1231##!!" throws a java.io.FileNotFoundException and would then try "d:\test1231##!!!\test.jar" which does exist. This way it does not matter if there are "!" in the file path or in the jar's files.
Alternatively the "!/" can be switched to something else that is an illegal file name or to something specific (like "jarpath:").
Alternatively make the jar's file path use another parameter.
Note:
It may be possible to override something, swap a handler, or change the code to open the file first then look inside the jar file later but I have not looked.
I am working in a java code that was designed to run on windows and contains a lot of references to files using windows style paths "System.getProperty("user.dir")\trash\blah". I am in charge to adapt it and deploy in linux. Is there an efficient way to convert all those paths(\) to unix style (/) like in "System.getProperty("user.dir")/trash/blah". Maybe, some configuration in java or linux to use \ as /.
My approach is to use the Path object to hold the path information, handle concatenate and relative path. Then, call Path's toString() to get the path String.
For converting the path separator, I preferred to use the apache common io library's FilenameUtils. It provides the three usefule functions:
String separatorsToSystem(String path);
String separatorsToUnix(String path);
String separatorsToWindows(String path)
Please look the code snippet, for relative path, toString, and separator changes:
private String getRelativePathString(String volume, Path path) {
Path volumePath = Paths.get(configuration.getPathForVolume(volume));
Path relativePath = volumePath.relativize(path);
return FilenameUtils.separatorsToUnix(relativePath.toString());
}
I reread your question and realize you likely don't need help writing paths. For what you're trying to do I am not able to find a solution. When I did this in a project recently I had to take time to convert all paths. Further, I made the assumption that working out of the "user.home" as a root directory was relatively sure to include write access for that user running my application. In any case, here are some path problems I addressed.
I rewrote the original Windows code like so:
String windowsPath = "C:\temp\directory"; //no permission or non-existing in osx or linux
String otherWindowsPath = System.getProperty("user.home") + "\Documents\AppFolder";
String multiPlatformPath = System.getProperty("user.home") + File.separator + "Documents" + File.separator + "AppFolder";
If you're going to be doing this in a lot of different places, perhaps write a utility class and override the toString() method to give you your unix path over and over again.
String otherWindowsPath = System.getProperty("user.home") + "\Documents\AppFolder";
otherWindowsPath.replace("\\", File.separator);
Write a script, replace all "\\" with a single forward slash, which Java will convert to the respected OS path.
I tried the use of Path interface;
//get a path object with relative path
Path filePath = Paths.get("C:\\Test\\filename.txt");
System.out.println("The file name is: " + filePath.getFileName());
Path filePath2 = Paths.get("/home/shibu/Desktop/filename.txt");
System.out.println("The file name is: " + filePath2.getFileName());
The out put is like;
The file name is: C:\Test\filename.txt
The file name is: filename.txt
For the windows file it printed full path and for linux file it printed only file name.
Why this difference?
Simple: On Linux, the only illegal characters in a file name are / and the 0 byte. Everything else, including \, line feed and escape sequences, are valid.
That means C:\Test\filename.txt is a valid file name on Linux. The Java runtime doesn't attempt to be smart and guess that this might be a Windows path.
Note that this is different when you use /: This is a valid path delimiter on Windows when using Java. So the path a/foo.txt is a relative path both on Linux and Windows.
This means you can open files on Windows using Paths.get("/C:/Test/filename.txt");, for example.
I am attempting to access a file within my current working directory.
The error I am getting is
[java] java.io.FileNotFoundException: /u/user/Documents/DataComProject1\confA.txt
The line which is causing this, I presume is:
bufferedReader = new BufferedReader(new FileReader(System.getProperty("user.dir") + "/" + fileName));
Whenever I print the directory I'm attempting to use with FileReader() I get:
/u/user/Documents/DataComProject1/confA.txt
I believe the problem has to do with the the backslash before the text file name being in a different direction. Upon looking in the directory I can see the file is there.
You could use the Path library instead of creating the path yourself:
Path p = Paths.get(System.getProperty("user.dir"))
.resolve(filename);
File f = p.toFile();
Don't write "/" to separate path elements, use this instead to get the correct path separator that's appropriate for your platform:
File.separator
It looks like your fileName includes an embedded backslash: it's Documents/DataComProject1\confA.txt. Since the backslash is a valid character in a file name, DataComProject1\confA.txt is assumed to be name of the file, not a name of a file in a directory.
To fix the file name you have to change the embedded \\ into the correct separator character:
fileName = fileName.replace('\\', File.separator);
bufferedReader = new BufferedReader(new FileReader(System.getProperty("user.dir") + "/" + fileName));
It would be correct to use the slash / as the directory separator on every system, but here I use File.separator because not using a hardcoded value makes the intent of the code clearer.
I'm trying to list a directory's contents, and rename certain files.
public void run(String dirName) {
try {
File parDir = new File(dirName);
File[] dirContents = parDir.listFiles();
// Rename if necessary
for(File f : dirContents) {
System.out.println("f is:\n" + f.toString());
String name = f.getName();
String subbedName = name.replaceAll("\uFFFD", "_");
System.out.println("\n" + "name = " + name + ", subbedName = " + subbedName + "\n");
if(!name.equals(subbedName)) {
File newFile = new File(f.getParentFile(), subbedName);
System.out.println("newFile is:\n" + newFile.toString());
if(!f.renameTo(newFile))
System.out.println("Tried to change file name but couldn't.");
}
}
}
catch(Exception exc1) {
System.out.println("Something happened while listing and renaming directory contents: " + exc1.getMessage());
}
}
When I run this, I get "Tried to change file name but couldn't." I don't believe that Java is considering these files to be "open", so I don't think that's the reason. I've even ran chmod 777 myDir where myDir is the value of the dirName string passed into the run method.
What am I missing here? Why won't Java rename these file(s)? These are CentOS machines.
Edit: Added printouts for both f and newFile, which is as follows:
f is:
/root/path/to/mydir/test�.txt
newFile is:
/root/path/to/mydir/test_.txt
You need to create your new File object with the full pathname of those files. So
String name = f.getName(); // gets the name without the directory
should likely be:
String name = f.getAbsolutePath();
(your search/replace may need to change)
The problem is that f.getName() returns the last name component of the path that is represented by f. You then massage this String and turn it back into a File. But the File now represents a path relative to the current directory, not the directory containing the original path.
As a result your code is actually attempting to rename the files from dirName into the application's current directory. That could fail because files already exist in the current directory with those names, or because the dirName and the current directory are in different file systems. (You cannot rename a file from one filesystem to another ... you have to copy it.)
Please note that a File in Java represents a pathname, not a file or a folder. In your code, the f objects are the pathnames for file system objects (either files or folders) in the directory denoted by the String dirname. Each of these f objects will have a directory part.
There is more than one way to fix your code; for example
change name = f.getName() to name = f.toString()
change new File(subbedName) to new File(f.getParentFile(), subbedName)
I have an alternative / additional theory.
The pathname of the file containing the \uFFFD character is coming out as "mojibake"; i.e. the kind of garbled text that you get when you display encoded text using the wrong encoding. And since we are seeing 3 characters of garbled text, I suspect that it is attempting to display the UTF-8 rendering of \uFFFD as Latin-1.
So my theory is that the same think is happening when the File.renameTo method is converting f to the form that it is going to provide to the system call. For some reason that is no clear to me, Java could be using the wrong encoding, and as a result producing a "name" for the original file that doesn't match the name of the file in the file system. That would be sufficient to cause the rename to fail.
Possibly related questions / links:
File name charset problem in java
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4733494 (Note that Sun decided this was not a Java bug, and most of the "me too" comments on the bug report are from people who do not understand the explanation ...)
f.getName(); only returns the name of the folder, not the full path. So subbedName becomes a relative path file. Try something with f.getCanonicalPath() instead.