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?
Advertisement
Answer
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.