Load a file in Resources with FileInputStream

Diegolo picture Diegolo · Oct 4, 2013 · Viewed 38.5k times · Source

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?

Answer

René Link picture René Link · Oct 4, 2013

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.