4

I have spent hours and hours searching for the answer and I just can't figure it out, I am trying to copy my resources folder which contains all the images and data files for my game I am working on out of the running jar and into E:/Program Files/mtd/ It works fine when I run it out of eclipse, but when I export the jar and try it, I get NoSuchFileException

`JAR
Installing...
file:///C:/Users/Cam/Desktop/mtd.jar/resources to file:///E:/Program%20Files/mtd
/resources
java.nio.file.NoSuchFileException: C:\Users\Cam\Desktop\mtd.jar\resources
        at sun.nio.fs.WindowsException.translateToIOException(Unknown Source)
        at sun.nio.fs.WindowsException.rethrowAsIOException(Unknown Source)
        at sun.nio.fs.WindowsException.rethrowAsIOException(Unknown Source)
        at sun.nio.fs.WindowsFileAttributeViews$Basic.readAttributes(Unknown Sou
rce)
        at sun.nio.fs.WindowsFileAttributeViews$Basic.readAttributes(Unknown Sou
rce)
        at sun.nio.fs.WindowsFileSystemProvider.readAttributes(Unknown Source)
        at java.nio.file.Files.readAttributes(Unknown Source)
        at java.nio.file.FileTreeWalker.walk(Unknown Source)
        at java.nio.file.FileTreeWalker.walk(Unknown Source)
        at java.nio.file.Files.walkFileTree(Unknown Source)
        at java.nio.file.Files.walkFileTree(Unknown Source)
        at me.Zacx.mtd.main.Game.<init>(Game.java:94)
        at me.Zacx.mtd.main.Game.main(Game.java:301)`

This is the code I am using:

    if (!pfFolder.exists()) {
    pfFolder.mkdir();
    try {

        URL url = getClass().getResource("/resources/");
        URI uri = null;

        if (url.getProtocol().equals("jar")) {
            System.out.println("JAR");
            JarURLConnection connect = (JarURLConnection) url.openConnection();
            uri = new URI(connect.getJarFileURL().toURI().toString() + "/resources/");

        } else if (url.getProtocol().equals("file")) {
            System.out.println("FILE");
            uri = url.toURI();
        }

        final Path src = Paths.get(uri);
        final Path tar = Paths.get(System.getenv("ProgramFiles") + "/mtd/resources/");

        System.out.println("Installing...");
        System.out.println(src.toUri() + " to " + tar.toUri());

        Files.walkFileTree(src, new SimpleFileVisitor<Path>() {
            public FileVisitResult visitFile( Path file, BasicFileAttributes attrs ) throws IOException {
                return copy(file);
            }
            public FileVisitResult preVisitDirectory( Path dir, BasicFileAttributes attrs ) throws IOException {
                return copy(dir);
            }
            private FileVisitResult copy( Path fileOrDir ) throws IOException {
                System.out.println("Copying " + fileOrDir.toUri() + " to " + tar.resolve( src.relativize( fileOrDir ) ).toUri());
                Files.copy( fileOrDir, tar.resolve( src.relativize( fileOrDir ) ) );
                return FileVisitResult.CONTINUE;
            }
        });
        System.out.println("Done!");
    } catch (IOException e) {
        e.printStackTrace();
    } catch (URISyntaxException e) {
        e.printStackTrace();
    }
}
Zacx
  • 420
  • 5
  • 10
  • `%20` is a HTML-escaped space, is it that what gives you problems (probably comes from the URL encoding through `.toUri()`)? Where exactly is line 94? `(Game.java:94)` Is `C:\Users\Cam\Desktop\mtd.jar\resources` a folder or a file? – Maximilian Gerhardt Mar 26 '16 at 17:39
  • @MaximilianGerhardt using .replaceAll(...) to remove the %20s doesn't fix it, Game.java:94 is Files.walkFileTree(src, new SimpleFileVisitor() { and resources is a folder that contains all the images and such for my game. Sorry for not including these :/ – Zacx Mar 26 '16 at 17:43
  • So, isn't Java right when saying `java.nio.file.NoSuchFileException: C:\Users\Cam\Desktop\mtd.jar\resources`, because `\resources` is indeed a folder, and no directory? There must be some error in the way you walk the file tree. Maybe try to create to create the folder first? Your `copy()` function doesn't seem to be called, or else we would see a `Copying .. to ...` output line *OR* it's crashing in that exact `System.out.println()` line in the `.resolve()` or `relativize()` function. Add more debugging output to make sure. – Maximilian Gerhardt Mar 26 '16 at 17:49
  • @MaximilianGerhardt The problem is that it does not see the resources folder that is inside the jar, the folder is 100% there, java just can't seem to see it and I don't know why do I have to do something differently since it is a folder and not a file? – Zacx Mar 26 '16 at 17:56
  • The reason it works in Eclipse is that all of the resources exist as individual files in the file system, so you always follow the `file` protocol path. When everything is packaged into the jar, you cannot just tree-walk it; a standard tree walk would just report the jar as a regular file and move on to the next entry. You must "open" the jar as its own file system in order to tree-walk inside it. – AJNeufeld Mar 26 '16 at 18:34
  • @AJNeufeld When I try your solution, I get `java.nio.file.ProviderNotFoundException: Provider not found` on this line `try (FileSystem fs = FileSystems.newFileSystem(Paths.get(System.getProperty("java.class.path")), null)) {` – Zacx Mar 26 '16 at 19:20

3 Answers3

1

The problem here is different File Systems. C:/Users/Cam/Desktop/mtd.jar is a File in the WindowsFileSystem. Since it is a file, and not a directory, you cannot access a subdirectory inside the file; C:/Users/Cam/Desktop/mtd.jar/resources is only a valid Path if mtd.jar is actually a directory instead of a file.

In order to access something on a different file system, you must use the path from the root of that file system. For example, if you have a file in D:\dir1\dir2\file, you cannot reach it using a path that begins with C:\ (symbolic links not withstanding); you must use a path that starts at the root of that file system D:\.

A jar file is just a file. It can be located anywhere within a file system, and can be moved, copied or deleted like any regular file. However, it contains within itself its own file system. There is no windows path that can be used to reference any file inside the jar's file system, just like no path starting at C:\ can reference any file within the D:\ file system.

In order to access the contents of a jar, you must open the jar as a ZipFileSystem.

// Autoclose the file system at end of try { ... } block.
try(FileSystem zip_fs = FileSystems.newFileSystem(pathToZipFile, null)) {
}

Once you have zip_fs, you can use zip_fs.getPath("/path/in/zip"); to get a Path to a file within it. This Path object will actually be a ZipFileSystemProvider path object, not a WindowsFileSystemProvider path object, but otherwise it is a Path object that can be opened, read from, etc., at least until the ZipFileSystem is closed. The biggest differences are that path.getFileSystem() will return the ZipFileSystem, and that resolve() and relativize() cannot use path objects where getFileSystem() returns different file systems.

When your project ran from Eclipse, all the resources were in the WindowsFileSystem, so walking the file system tree and copying the resources was straight forward. When your project ran from a jar, the resources were not in the default file system.

Here is a Java class that will copy resources to an installation directory. It will work in Eclipse (with all the resources as individual files), as well as when the application is packaged into a jar.

public class Installer extends SimpleFileVisitor<Path> {

    public static void installResources(Path dst, Class<?> cls, String root) throws URISyntaxException, IOException {
        URL location = cls.getProtectionDomain().getCodeSource().getLocation();
        if (location.getProtocol().equals("file")) {
            Path path = Paths.get(location.toURI());
            if (location.getPath().endsWith(".jar")) {
                try (FileSystem fs = FileSystems.newFileSystem(path, null)) {
                    installResources(dst, fs.getPath("/" + root));
                }
            } else {
                installResources(dst, path.resolve(root));
            }
        } else {
            throw new IllegalArgumentException("Not supported: " + location);
        }
    }

    private static void installResources(Path dst, Path src) throws IOException {
        Files.walkFileTree(src, new Installer(dst, src));
    }

    private final Path target, source;

    private Installer(Path dst, Path src) {
        target = dst;
        source = src;
    }

    private Path resolve(Path path) {
        return target.resolve(source.relativize(path).toString());
    }

    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
        Path dst = resolve(dir);
        Files.createDirectories(dst);
        return super.preVisitDirectory(dir, attrs);
    }

    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
        Path dst = resolve(file);
        Files.copy(Files.newInputStream(file), dst, StandardCopyOption.REPLACE_EXISTING);
        return super.visitFile(file, attrs);
    }
}

Called as:

    Path dst = Paths.get("C:\\Program Files\\mtd");
    Installer.installResources(dst, Game.class, "resources");
Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
AJNeufeld
  • 8,526
  • 1
  • 25
  • 44
  • When I try this solution, I get this output `file:///C:/Users/Cam/Desktop/eclipse/Work/Meme%20Tower%20Defence/bin/ to file:///E:/Program%20Files/mtd/resources file:/C:/Users/Cam/Desktop/eclipse/Work/Meme%20Tower%20Defence/bin/ Exception in thread "main" java.nio.file.ProviderNotFoundException: Provider not found` Current code: `System.out.println(src.toUri() + " to " + tar.toUri()); System.out.println(uri); try (FileSystem fs = FileSystems.newFileSystem(Paths.get(uri), null)) { Path path = fs.getPath("/resources/"); Files.walkFileTree(path, new SimpleFileVisitor() {` – Zacx Mar 26 '16 at 19:55
  • You have a `uri` to the jar? Try `FileSystems.newFileSystem(uri, new HashMap<>());` version of the call. – AJNeufeld Mar 26 '16 at 20:14
  • I'm not sure what you mean by that, could you elaborate a little more? Sorry again, I'm new to file systems. – Zacx Mar 26 '16 at 20:21
  • @Zacx I've extended my example to show an example. You'll have to add in the resource copying. – AJNeufeld Mar 26 '16 at 21:07
1

This was harder that I thought, but here is how to do it.

here is my copy method Reference https://examples.javacodegeeks.com/core-java/io/file/4-ways-to-copy-file-in-java/

public void copyFile(String inputPath, String outputPath ) throws IOException
{

    InputStream inputStream = null;

    OutputStream outputStream = null;
    try {

        inputStream =  getClass().getResourceAsStream(inputPath);
        outputStream = new FileOutputStream(outputPath);

        byte[] buf = new byte[1024];

        int bytesRead;

        while ((bytesRead = inputStream.read(buf)) > 0) {

            outputStream.write(buf, 0, bytesRead);

       }

    }
    finally {


        inputStream.close();

        outputStream.close();

    }

Please note the structure of the project of the Jar file in this image Project structure

Now I need to read the Jar file. This is a varition on this solution How can I get a resource "Folder" from inside my jar File? . Both of these methods work together to product the result. I have tested this and it works.

public class Main {

public static void main(String[] args) throws IOException {
    // TODO Auto-generated method stub

    final String pathPartOne = "test/com";
    final String pathPartTwo = "/MyResources";
    String pathName = "C:\\Users\\Jonathan\\Desktop\\test.jar";

    JarTest test = new JarTest();

    final File jarFile = new File(pathName);

    if(jarFile.isFile()) {  // Run with JAR file
        final JarFile jar = new JarFile(jarFile);
        final Enumeration<JarEntry> entries = jar.entries(); //gives ALL entries in jar
        while(entries.hasMoreElements()) {
            final String name = entries.nextElement().getName();

            if (name.startsWith(pathPartOne+pathPartTwo + "/")) { //filter according to the path
                if(name.contains("."))//has extension
                {
                    String relavtivePath = name.substring(pathPartOne.length()+1);
                    String fileName = name.substring(name.lastIndexOf('/')+1);
                    System.out.println(relavtivePath);
                    System.out.println(fileName);
                    test.copyFile(relavtivePath, "C:\\Users\\Jonathan\\Desktop\\" + fileName);
                }

            }
        }
        jar.close();
    } 


}

}

Hope that helps.

Community
  • 1
  • 1
  • That will read a file, I have done the same with images and such, however I am trying to copy a folder from inside my jar, to a directory outside my jar. – Zacx Mar 26 '16 at 19:30
  • Yes but I believe that the same principle applies here. You first need to get the file that you want to copy by getting the path. Once you have the path you should be sorted. I don't believe "C:\Users\Cam\Desktop\mtd.jar\resources" is valid path – Jonathan.Hickey Mar 26 '16 at 19:38
  • In theory, it should be a vaild path since there is a folder called resources inside of mtd.jar, sorry if I'm completely off, I'm new to the file systems libraries and I'm 14 so I don't have years of experience behind me that I can use to figure this out unfortunately :( – Zacx Mar 26 '16 at 19:43
  • Have a look at http://stackoverflow.com/questions/11012819/how-can-i-get-a-resource-folder-from-inside-my-jar-file once you have the have the folder the rest should be easy – Jonathan.Hickey Mar 26 '16 at 20:23
-2

I FINALLY FOUND THE ANSWER I don't want to type out a big, long explanation but for anyone looking for the solution, here it is

 `  
              //on startup
               installDir("");
                for (int i = 0; i < toInstall.size(); i++) {
                    File f = toInstall.get(i);
                    String deepPath = f.getPath().replace(f.getPath().substring(0, f.getPath().lastIndexOf("resources") + "resources".length() + 1), "");
                    System.out.println(deepPath);
                    System.out.println("INSTALLING: " + deepPath);
                    installDir(deepPath);
                    System.out.println("INDEX: " + i);
                }

public void installDir(String path) {
            System.out.println(path);
            final URL url = getClass().getResource("/resources/" + path);
            if (url != null) {
                try {
                    final File apps = new File(url.toURI());
                    for (File app : apps.listFiles()) {
                        System.out.println(app);
                            System.out.println("copying..." + app.getPath() + " to " + pfFolder.getPath());
                            String deepPath = app.getPath().replace(app.getPath().substring(0, app.getPath().lastIndexOf("resources") + "resources".length() + 1), "");
                            System.out.println(deepPath);

                        try {

                                File f = new File(resources.getPath() + "/" + deepPath);
                                if (getExtention(app) != null) {
                                FileOutputStream resourceOS = new FileOutputStream(f);
                                byte[] byteArray = new byte[1024];
                                int i;
                                InputStream classIS = getClass().getClassLoader().getResourceAsStream("resources/" + deepPath);
                        //While the input stream has bytes
                                while ((i = classIS.read(byteArray)) > 0) 
                                {
                        //Write the bytes to the output stream
                                    resourceOS.write(byteArray, 0, i);
                                }
                        //Close streams to prevent errors
                                classIS.close();
                                resourceOS.close();
                                } else {
                                    System.out.println("new dir: " + f.getPath() + " (" + toInstall.size() + ")");
                                    f.mkdir();
                                    toInstall.add(f);
                                    System.out.println(toInstall.size());
                                }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                } catch (URISyntaxException ex) {
                    // never happens
                }
            }

        }`
Zacx
  • 420
  • 5
  • 10