I know the safe way to open a file in the resources is:
InputStream is = this.getClass().getResourceAsStream("/path/in/jar/file.name");
now the problem is that my file is a model for a decider in the Weka Wrapper package and the Decider class has only a method:
public void load(File file) throws Exception
load takes the file and opens it as a FileInputStream. Do you see a workaround? I really would like to ship the model putting it in the resources. I was thinking to create a temporary file, write the content of the model in the temp file and then pass the temporary file to Weka, but it is so dirty.. other options?
I see 2 solutions:
Solution 1
Read the classpath ressource to a temp file and delete it after you called load(File)
InputStream cpResource = this.getClass().getClassLoader().getResourceAsStream("file.name");
File tmpFile = File.createTempFile("file", "temp");
FileUtils.copyInputStreamToFile(cpResource, tmpFile); // FileUtils from apache-io
try {
decider.load(tmpFile);
} finally {
tmpFile.delete();
}
Solution 2
If the ClassLoader that loads the resource is a URLClassLoader you can try to find the absolute file name. But this only works if the resource you want exists as a file on the filesystem. It doesn't work if the file is contained in a jar.
ClassLoader classLoader = this.getClass().getClassLoader();
if(classLoader instanceof URLClassLoader){
URLClassLoader urlClassLoader = URLClassLoader.class.cast(classLoader);
URL resourceUrl = urlClassLoader.findResource("file.name");
if("file".equals(resourceUrl.getProtocol())){
URI uri = resourceUrl.toURI();
File file = new File(uri);
decider.load(file);
}
}
I would suggest to write a utility class that tries to find the absolute file through the class loader or if it can't get it this way uses the temp file approach as fallback.
Or in a more object-oriented way:
public class FileResourceTest {
public static void main(String[] args) throws IOException {
File resourceAsFile = getResourceAsFile("file.name");
System.out.println(resourceAsFile);
}
private static File getResourceAsFile(String resource) throws IOException {
ClassLoader cl = FileResourceTest.class.getClassLoader();
File file = null;
FileResource fileResource = new URLClassLoaderFileResource(cl, resource);
try {
file = fileResource.getFile();
} catch (IOException e) {
fileResource = new ClasspathResourceFileResource(cl, resource);
file = fileResource.getFile();
}
return file;
}
public static interface FileResource {
public File getFile() throws IOException;
}
public static class ClasspathResourceFileResource implements FileResource {
private ClassLoader cl;
private String resource;
public ClasspathResourceFileResource(ClassLoader cl, String resource) {
this.cl = cl;
this.resource = resource;
}
public File getFile() throws IOException {
InputStream cpResource = cl.getResourceAsStream(resource);
File tmpFile = File.createTempFile("file", "temp");
FileUtils.copyInputStreamToFile(cpResource, tmpFile);
tmpFile.deleteOnExit();
return tmpFile;
}
}
public static class URLClassLoaderFileResource implements FileResource {
private ClassLoader cl;
private String resource;
public URLClassLoaderFileResource(ClassLoader cl, String resourcePath) {
this.cl = cl;
this.resource = resourcePath;
}
public File getFile() throws IOException {
File resourceFile = null;
if (cl instanceof URLClassLoader) {
URLClassLoader urlClassLoader = URLClassLoader.class.cast(cl);
URL resourceUrl = urlClassLoader.findResource(resource);
if ("file".equals(resourceUrl.getProtocol())) {
try {
URI uri = resourceUrl.toURI();
resourceFile = new File(uri);
} catch (URISyntaxException e) {
IOException ioException = new IOException(
"Unable to get file through class loader: "
+ cl);
ioException.initCause(e);
throw ioException;
}
}
}
if (resourceFile == null) {
throw new IOException(
"Unable to get file through class loader: " + cl);
}
return resourceFile;
}
}
}
You can also use a thrid party library like commons-vfs that allows you to reference a file within a jar. E.g. jar:// arch-file-uri[! absolute-path]
. Since commons-vfs specifies an own FileObject
that represents a file you must still copy the content to a local java.io.File
to adapt to the Decider.load(File)
API.
EDIT
This is very helpful! Is there anything in newer versions of java that already supports this requirement?
Even if I haven't take a look at every class of every newer version I would say no. Because a classpath resource is not always a file. E.g. it can be a file within a jar or even a remote resource. Think about the applets that java programmers used a long time ago. Thus the concept of a classpath and it's resources is not bound to a local filsystem. This is obviously a good thing, because you can load classes from almost every URL and this makes it more flexible. But this flexibility also means that you must read the resource and create a copy if you need a File
.
But maybe some kind of utility code like the one I showed above will make it into the JRE. Maybe it is already there. If so please comment and let us all know.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With