5

I built a quite fat JavaFX application (the JAR is about 128 MB) and I got no problem in running in through IntelliJ. But when I run it from the terminal my 3D model loaders (Fxyz3d library) launch this exception.

Exception in thread "JavaFX Application Thread" java.nio.file.FileSystemNotFoundException
    at jdk.zipfs/jdk.nio.zipfs.ZipFileSystemProvider.getFileSystem(ZipFileSystemProvider.java:172)
    at jdk.zipfs/jdk.nio.zipfs.ZipFileSystemProvider.getPath(ZipFileSystemProvider.java:158)
    at java.base/java.nio.file.Path.of(Path.java:208)
    at java.base/java.nio.file.Paths.get(Paths.java:98)
    at org.fxyz3d.importers.obj.ObjImporter.read(ObjImporter.java:115)
    at org.fxyz3d.importers.obj.ObjImporter.loadAsPoly(ObjImporter.java:102)
    at org.fxyz3d.importers.Importer3D.loadIncludingAnimation(Importer3D.java:160)
    at org.fxyz3d.importers.Importer3D.loadAsPoly(Importer3D.java:80)
    at it.polimi.ingsw.PSP50.View.GUI.GuiView.lambda$startingGame$1(GuiView.java:201)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96) 

This is thrown only for the 3D-object Loader from the Fxyz3d library, not for my other normal FXML loaders. I'm using the same way to get the files from my src/main/resources folder, that is getClass().getResource. So is this really a path issue? or is it a library issue? In IntelliJ there's no problem at all instead, it all works fine. Here it's the part of the code that doesn't work:

Model3D boardModel = Importer3D.loadAsPoly(getClass().getResource("/boardcliff2.obj"));
 

If anyone has encountered something like this before and knows what it going on, help would be really appreciated

anonymflaco
  • 158
  • 1
  • 10
  • Don’t assume anything about the current working directory. – Thorbjørn Ravn Andersen Jun 27 '20 at 14:13
  • 1
    According to the stack trace you posted in your question, method `read()` in class `org.fxyz3d.importers.obj.ObjImporter` calls method `get()` of class `java.nio.file.Paths` which eventually leads to the exception being thrown. What arguments do you supply when you invoke method `Paths.get()`? (Refer to line 115 in file `ObjImporter.java`) – Abra Jun 27 '20 at 14:37
  • 3
    There was some [refactoring](https://github.com/FXyz/FXyz/commit/a8e519acc06328d8d1f55675df8cb9f8ff94cfc3#diff-a5ab33f08c3367762325fe178b0af5c3) and a `BufferedReader` was replaced with `Files.lines` to read the model files. The FXyz samples viewer works for the [ImportObj sample](https://github.com/FXyz/FXyz/blob/master/FXyz-Samples/src/main/java/org/fxyz3d/samples/importers/ImportOBJ.java#L49) using gradle and the JavaFX plugin, but I get a similar error when using `jlink`, so yes, the best for now is filing an issue. – José Pereda Jun 27 '20 at 14:59
  • so do you think there's no solution for the moment Sir? – anonymflaco Jun 27 '20 at 16:12
  • 2
    As a workaround, you could add some code to extract the obj/mtl files to a tmp location, then use an url with `file://` protocol instead. – José Pereda Jun 27 '20 at 19:00
  • I added a community answer showing the suggested workaround. But please [submit an issue](https://github.com/FXyz/FXyz/issues) to the _FXyz3D_ library. – Slaw Jun 28 '20 at 04:36
  • 2
    @José Just to note, the issue (I assume) you see when using a linked runtime image should [no longer occur](https://bugs.openjdk.java.net/browse/JDK-8224946) in Java 13+. However, the error encountered by the OP will still happen when the resource is embedded in a JAR file. – Slaw Jun 28 '20 at 06:38
  • 1
    Thanks @Slaw, indeed it has been fixed for jlink. I filed the bug: https://github.com/FXyz/FXyz/issues/108. – José Pereda Jun 28 '20 at 11:23

2 Answers2

3

José Pereda opened an issue for this which has since been fixed. As of now (14 Aug 2020), the latest version of FXyz is 0.5.2 which does not include the fix for this issue. You can continue to use the workaround shown in this answer, build the library yourself from the most recent commit, or wait for the library's next release.


This appears to be an issue with the implementation. It attempts to convert the URL into a Path but the necessary FileSystem does not exist1. The best workaround probably is to extract the resource into a temporary file and then import the object from said file. That way the URL will have a file: protocol and the conversion to Path will work (the default FileSystem always exists). Here's an example of how you could extract the resource:

// Note: 'Path' is 'java.nio.file.Path', not 'javafx.scene.shape.Path'
public static Path copyToTempFile(URL url, String suffix) throws IOException {
  // 'suffix' will default to ".tmp" if null
  Path tempFile = Files.createTempFile(null, suffix);
  try (InputStream in = url.openStream();
       OutputStream out = Files.newOutputStream(tempFile)) {
    in.transferTo(out); // 'transferTo' method added in Java 9
  }
  return tempFile;
}

Then you can use the resulting Path to import the 3D object:

Path tempFile = copyToTempFile(getClass().getResource("/boardcliff2.obj"), ".obj");
Model3D boardModel = Importer3D.loadAsPoly(tempFile.toUri().toURL());

If desired, you can delete the temporary file when finished with it.


1. When embedded in a JAR file a resource's URL has the jar: protocol. This means a FileSystem from the ZIP FileSystemProvider, open to the specific ZIP/JAR file, must exist in order for the conversion to Path to work. This issue would not occur if the implementation simply used URL#openStream(), which accesses the JAR entry via a different mechanism.

Slaw
  • 37,820
  • 8
  • 53
  • 80
0

Besides Slaw's solution, a colleague of mine found an alternative solution: adding these lines of code to the function loadAsPoly from the ObjImporter class (Fxyz3d Library) fixed the problem for every import.

public class ObjImporter implements Importer {
    ...
    @Override
    public Model3D loadAsPoly(URL url) throws IOException {
        // Additional lines
             if(url.getProtocol().equals("jar")) {
                            try {
                                Map<String, String> env = new HashMap<>();
                                env.put("create", "true");
                                FileSystem zipfs = FileSystems.newFileSystem(url.toURI(), env);
                            } catch(FileSystemAlreadyExistsException ignored) {

                            } catch (IOException | URISyntaxException e) {
                                e.printStackTrace();
                            }
                        }
        // End of the addition
        return read(url, true);
    }
   ...
}

I tested it with my Jar and it's working as well(of course in my project I imported the necessary classes from the Fxyz library into my Utils package).

anonymflaco
  • 158
  • 1
  • 10
  • 1
    Note that, instead of copying files from FXyz3D into your project, it may be easier to create a utility method that does the above and then invokes `Importer3D.loadAsPoly`. – Slaw Jun 28 '20 at 17:05