javajarweka

Load a file in Resources with FileInputStream


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?


Solution

  • 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.