java.nio.file.Path para um recurso de caminho de classe

143

Existe uma API para obter um recurso de caminho de classe (por exemplo, o que eu obteria Class.getResource(String)) como um java.nio.file.Path? Idealmente, eu gostaria de usar as novas PathAPIs sofisticadas com recursos de caminho de classe.

Louis Wasserman
fonte
3
Bem, tomando o caminho longo (trocadilhos), você tem Paths.get(URI), então, 'URL.toURI () , and last getResource () `, que retorna a URL. Você poderá encadear essas pessoas. Ainda não tentei.
NilsH

Respostas:

174

Este funciona para mim:

return Paths.get(ClassLoader.getSystemResource(resourceName).toURI());
keyoxy
fonte
7
@VGR se os recursos no arquivo .jar puderem tentar este `Resource resource = new ClassPathResource (" use.txt "); BufferedReader reader = new BufferedReader (new InputStreamReader (resource.getInputStream ())); `favor consulte stackoverflow.com/questions/25869428/…
zhuguowei
8
@zhuguowei é uma abordagem específica da Primavera. Não funciona quando o Spring não está sendo usado.
Ryan J. McDonough
2
Se seu aplicativo não contam com o carregador de classe do sistema, ele deve serThread.currentThread().getContextClassLoader().getResource(resourceName).toURI()
ThrawnCA
27

Supondo que o que você deseja fazer é chamar Files.lines (...) em um recurso que vem do caminho de classe - possivelmente de dentro de um jar.

Como o Oracle complicou a noção de quando um Path é um Path, não fazendo com que getResource retorne um caminho utilizável se ele residir em um arquivo jar, o que você precisa fazer é algo como isto:

Stream<String> stream = new BufferedReader(new InputStreamReader(ClassLoader.getSystemResourceAsStream("/filename.txt"))).lines();
user2163960
fonte
1
se o "/" anterior é necessário no seu caso, não sei, mas no meu caso class.getResourcerequer uma barra, mas getSystemResourceAsStreamnão consigo encontrar o arquivo quando prefixado com uma barra.
Adam
11

A solução mais geral é a seguinte:

interface IOConsumer<T> {
    void accept(T t) throws IOException;
}
public static void processRessource(URI uri, IOConsumer<Path> action) throws IOException {
    try {
        Path p=Paths.get(uri);
        action.accept(p);
    }
    catch(FileSystemNotFoundException ex) {
        try(FileSystem fs = FileSystems.newFileSystem(
                uri, Collections.<String,Object>emptyMap())) {
            Path p = fs.provider().getPath(uri);
            action.accept(p);
        }
    }
}

O principal obstáculo é lidar com as duas possibilidades: ter um sistema de arquivos existente que devemos usar, mas não fechar (como fileURIs ou o armazenamento do módulo do Java 9), ou ter que abrir e fechar com segurança o sistema de arquivos (como arquivos zip / jar).

Portanto, a solução acima encapsula a ação real em um interface, lida com os dois casos, fechando com segurança depois no segundo caso e funciona do Java 7 ao Java 10. Ele investiga se já existe um sistema de arquivos aberto antes de abrir um novo, portanto também funciona no caso de outro componente do seu aplicativo já ter aberto um sistema de arquivos para o mesmo arquivo zip / jar.

Pode ser usado em todas as versões Java mencionadas acima, por exemplo, para listar o conteúdo de um pacote ( java.langno exemplo) como Paths, desta forma:

processRessource(Object.class.getResource("Object.class").toURI(), new IOConsumer<Path>() {
    public void accept(Path path) throws IOException {
        try(DirectoryStream<Path> ds = Files.newDirectoryStream(path.getParent())) {
            for(Path p: ds)
                System.out.println(p);
        }
    }
});

Com o Java 8 ou mais recente, você pode usar expressões lambda ou referências a métodos para representar a ação real, por exemplo

processRessource(Object.class.getResource("Object.class").toURI(), path -> {
    try(Stream<Path> stream = Files.list(path.getParent())) {
        stream.forEach(System.out::println);
    }
});

para fazer o mesmo.


A versão final do sistema de módulos do Java 9 quebrou o exemplo de código acima. O JRE inconsistentemente retorna o caminho /java.base/java/lang/Object.classpara o Object.class.getResource("Object.class")que deveria ser /modules/java.base/java/lang/Object.class. Isso pode ser corrigido acrescentando a falta /modules/quando o caminho pai é relatado como inexistente:

processRessource(Object.class.getResource("Object.class").toURI(), path -> {
    Path p = path.getParent();
    if(!Files.exists(p))
        p = p.resolve("/modules").resolve(p.getRoot().relativize(p));
    try(Stream<Path> stream = Files.list(p)) {
        stream.forEach(System.out::println);
    }
});

Em seguida, ele funcionará novamente com todas as versões e métodos de armazenamento.

Holger
fonte
1
Esta solução funciona muito bem! Posso confirmar que isso funciona com todos os recursos (arquivos, diretórios) nos caminhos de classe de diretório e nos caminhos de classe jar. Definitivamente, é assim que a cópia de muitos recursos deve ser feita no Java 7+.
Mitchell Skaggs
10

Acontece que você pode fazer isso, com a ajuda do provedor interno do Zip File System . No entanto, passar um URI de recurso diretamente para Paths.getnão funcionará; em vez disso, é necessário primeiro criar um sistema de arquivos zip para o URI do jar sem o nome da entrada e, em seguida, consultar a entrada nesse sistema de arquivos:

static Path resourceToPath(URL resource)
throws IOException,
       URISyntaxException {

    Objects.requireNonNull(resource, "Resource URL cannot be null");
    URI uri = resource.toURI();

    String scheme = uri.getScheme();
    if (scheme.equals("file")) {
        return Paths.get(uri);
    }

    if (!scheme.equals("jar")) {
        throw new IllegalArgumentException("Cannot convert to Path: " + uri);
    }

    String s = uri.toString();
    int separator = s.indexOf("!/");
    String entryName = s.substring(separator + 2);
    URI fileURI = URI.create(s.substring(0, separator));

    FileSystem fs = FileSystems.newFileSystem(fileURI,
        Collections.<String, Object>emptyMap());
    return fs.getPath(entryName);
}

Atualizar:

Foi corretamente apontado que o código acima contém um vazamento de recursos, pois o código abre um novo objeto FileSystem, mas nunca o fecha. A melhor abordagem é passar um objeto de trabalho semelhante ao consumidor, como a resposta de Holger faz. Abra o ZipFS FileSystem apenas o tempo suficiente para o trabalhador fazer o que for necessário com o Path (desde que o trabalhador não tente armazenar o objeto Path para uso posterior) e feche o FileSystem.

VGR
fonte
11
Tenha cuidado com o fs recém-criado. Uma segunda chamada usando o mesmo jar lançará uma exceção reclamando sobre um sistema de arquivos já existente. Será melhor tentar (FileSystem fs = ...) {return fs.getPath (entryName);} ou se você deseja que esse cache seja armazenado, faça um tratamento mais avançado. Na forma atual é arriscado.
raisercostin
3
Além da questão do novo sistema de arquivos potencialmente não fechado, as suposições sobre a relação entre os esquemas e a necessidade de abrir um novo sistema de arquivos e a confusão com o conteúdo do URI limitam a utilidade da solução. Configurei uma nova resposta que mostra uma abordagem geral que simplifica a operação e lida com novos esquemas como o novo armazenamento de classe Java 9 ao mesmo tempo. Ele também funciona quando alguém dentro do aplicativo já abriu o sistema de arquivos (ou o método é chamado duas vezes para o mesmo pote) ...
Holger
Dependendo do uso desta solução, o não fechado newFileSystempode levar a vários recursos abertos para sempre. Embora o adendo @raisercostin evite o erro ao tentar criar um sistema de arquivos já criado, se você tentar usar o retornado Path, receberá um ClosedFileSystemException. Resposta @Holger funciona bem para mim.
José Andias 18/04
Eu não fecharia o FileSystem. Se você carregar um recurso de um Jar e criar o necessário FileSystem- o FileSystemtambém permitirá que você carregue outros recursos do mesmo Jar. Além disso, depois de criar o novo, FileSystemvocê pode apenas tentar carregar o recurso novamente usando Paths.get(Path)e a implementação usará automaticamente o novo FileSystem.
NS du Toit
Ou seja, você não precisa usar o #getPath(String)método no FileSystemobjeto.
NS du Toit
5

Eu escrevi um pequeno método auxiliar para ler Pathsos recursos da sua classe. É bastante útil, pois só precisa de uma referência da classe em que você armazenou seus recursos, bem como do nome do próprio recurso.

public static Path getResourcePath(Class<?> resourceClass, String resourceName) throws URISyntaxException {
    URL url = resourceClass.getResource(resourceName);
    return Paths.get(url.toURI());
}  
Michael Stauffer
fonte
1

Você não pode criar URI a partir de recursos dentro do arquivo jar. Você pode simplesmente gravá-lo no arquivo temporário e usá-lo (java8):

Path path = File.createTempFile("some", "address").toPath();
Files.copy(ClassLoader.getSystemResourceAsStream("/path/to/resource"), path, StandardCopyOption.REPLACE_EXISTING);
user1079877
fonte
1

Leia um arquivo da pasta de recursos usando o NIO, em java8

public static String read(String fileName) {

        Path path;
        StringBuilder data = new StringBuilder();
        Stream<String> lines = null;
        try {
            path = Paths.get(Thread.currentThread().getContextClassLoader().getResource(fileName).toURI());
            lines = Files.lines(path);
        } catch (URISyntaxException | IOException e) {
            logger.error("Error in reading propertied file " + e);
            throw new RuntimeException(e);
        }

        lines.forEach(line -> data.append(line));
        lines.close();
        return data.toString();
    }
Real Coder
fonte
0

Você precisa definir o sistema de arquivos para ler o recurso do arquivo jar, conforme mencionado em https://docs.oracle.com/javase/8/docs/technotes/guides/io/fsp/zipfilesystemprovider.html . Tenho sucesso ao ler o recurso do arquivo jar com os códigos abaixo:

Map<String, Object> env = new HashMap<>();
try (FileSystem fs = FileSystems.newFileSystem(uri, env)) {

        Path path = fs.getPath("/path/myResource");

        try (Stream<String> lines = Files.lines(path)) {
            ....
        }
    }
user3050291
fonte