Получение FileSystemNotFoundException из ZipFileSystemProvider при создании пути к ресурсу

У меня есть проект Maven и внутри метода я хочу создать путь для каталога в моей папке ресурсов. Это делается следующим образом:

try {
    final URI uri = getClass().getResource("/my-folder").toURI();
    Path myFolderPath = Paths.get(uri);
} catch (final URISyntaxException e) {
    ...
}

Сгенерированный URI выглядит как jar:file:/C:/path/to/my/project.jar!/my-folder.

Элемент stacktrace выглядит следующим образом:

Exception in thread "pool-4-thread-1" java.nio.file.FileSystemNotFoundException
    at com.sun.nio.zipfs.ZipFileSystemProvider.getFileSystem(ZipFileSystemProvider.java:171)
    at com.sun.nio.zipfs.ZipFileSystemProvider.getPath(ZipFileSystemProvider.java:157)
    at java.nio.file.Paths.get(Paths.java:143)

URI представляется действительным. Часть перед ! указывает на сгенерированный jar файл и часть после него на my-folder в корневом каталоге архива. Ранее я использовал эти инструкции для создания путей к моим ресурсам. Почему теперь я получаю исключение?

Ответ 1

Вам необходимо создать файловую систему, прежде чем вы сможете получить доступ к пути в zip, например

final URI uri = getClass().getResource("/my-folder").toURI();
Map<String, String> env = new HashMap<>(); 
env.put("create", "true");
FileSystem zipfs = FileSystems.newFileSystem(uri, env);
Path myFolderPath = Paths.get(uri);

Это не делается автоматически.

См. http://docs.oracle.com/javase/7/docs/technotes/guides/io/fsp/zipfilesystemprovider.html

Ответ 2

Если вы собираетесь читать файл ресурсов, вы можете напрямую использовать getClass.getResourceAsStream. Это приведет к принудительной установке файловой системы. Функция возвращает null, если ваш ресурс не найден, иначе у вас есть входной поток для анализа вашего ресурса.

Ответ 3

Расширяясь на превосходном ответе @Uwe Allner, можно использовать отказоустойчивый метод

private FileSystem initFileSystem(URI uri) throws IOException
{
    try
    {
        return FileSystems.getFileSystem(uri);
    }
    catch( FileSystemNotFoundException e )
    {
        Map<String, String> env = new HashMap<>();
        env.put("create", "true");
        return FileSystems.newFileSystem(uri, env);
    }
}

Вызов этого с URI, который вы собираетесь загрузить, обеспечит работоспособность файловой системы. Я всегда вызываю FileSystem.close() после его использования:

FileSystem zipfs = initFileSystem(fileURI);
filePath = Paths.get(fileURI);
// Do whatever you need and then close the filesystem
zipfs.close();

Ответ 4

В дополнение к @Uwe Allner и @mvreijn:

Будьте осторожны с URI. Иногда URI имеет неправильный формат (например, "file:/path/...", а правильный - "file:///path/..."), и вы не можете получить правильный FileSystem.
В этом случае помогает URI создать метод Path toUri().

В моем случае я немного изменил метод initFileSystem и использовал в исключительных случаях FileSystems.newFileSystem(uri, Collections.emptyMap()). В исключительном случае используется FileSystems.getDefault().

В моем случае также нужно было поймать IllegalArgumentException, чтобы обработать случай с помощью Path component should be '/'. Исключение обнаружено в windows и linux, но работает FileSystems.getDefault(). В osx не возникает исключение и создается newFileSystem:

private FileSystem initFileSystem(URI uri) throws IOException {
    try {
        return FileSystems.newFileSystem(uri, Collections.emptyMap());
    }catch(IllegalArgumentException e) {
        return FileSystems.getDefault();
    }
}