Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Load a file in Resources with FileInputStream

Tags:

java

jar

weka

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?

like image 270
Diegolo Avatar asked Oct 04 '13 09:10

Diegolo


1 Answers

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.

like image 52
René Link Avatar answered Oct 02 '22 08:10

René Link