197 votes

java.nio.file.Path pour une ressource classpath

Existe-t-il une API pour obtenir une ressource de classpath (par exemple, ce que j'obtiendrais à partir de Class.getResource(String) ) comme un java.nio.file.Path ? Dans l'idéal, j'aimerais utiliser le nouveau système de Path API avec des ressources classpath.

263voto

keyoxy Points 3681

Celui-là marche pour moi :

return Path.of(ClassLoader.getSystemResource(resourceName).toURI());

31voto

user2163960 Points 103

Je suppose que ce que vous voulez faire, c'est appeler Files.lines(...) sur une ressource qui provient du classpath - éventuellement d'un jar.

Depuis qu'Oracle a alambiqué la notion de quand une Path est un Path en ne faisant pas getResource retourner un chemin utilisable s'il réside dans un fichier jar, ce que vous devez faire est quelque chose comme ceci :

Stream<String> stream = new BufferedReader(new InputStreamReader(ClassLoader.getSystemResourceAsStream("/filename.txt"))).lines();

16voto

Holger Points 13789

La solution la plus générale est la suivante :

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);
        }
    }
}

L'obstacle principal est de gérer les deux possibilités, soit, avoir un système de fichiers existant que nous devrions utiliser, mais pas proche (comme avec file URI ou le stockage des modules de Java 9), ou devoir ouvrir et donc fermer nous-mêmes le système de fichiers en toute sécurité (comme les fichiers zip/jar).

Par conséquent, la solution ci-dessus encapsule l'action réelle dans un fichier interface gère les deux cas, en fermant de manière sûre après dans le second cas, et fonctionne de Java 7 à Java 18. Il vérifie s'il existe déjà un système de fichiers ouvert avant d'en ouvrir un nouveau. Il fonctionne donc également dans le cas où un autre composant de votre application a déjà ouvert un système de fichiers pour le même fichier zip/jar.

Il peut être utilisé dans toutes les versions de Java nommées ci-dessus, par exemple pour énumérer le contenu d'un paquetage ( java.lang dans l'exemple) comme Path comme ceci :

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);
        }
    }
});

Avec Java 8 ou une version plus récente, vous pouvez utiliser des expressions lambda ou des références de méthode pour représenter l'action réelle, par exemple

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

de faire de même.


La version finale du système de modules de Java 9 s'est brisé l'exemple de code ci-dessus. Les versions de Java de 9 à 12 renvoient de manière incohérente le chemin /java.base/java/lang/Object.class pour Paths.get(Object.class.getResource("Object.class")) alors qu'il devrait être /modules/java.base/java/lang/Object.class . Cela peut être corrigé en ajoutant au début de l'écran le code manquant /modules/ lorsque le chemin parent est signalé comme inexistant :

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);
    }
});

Ensuite, il fonctionnera à nouveau avec toutes les versions et méthodes de stockage. À partir du JDK 13, cette solution de contournement n'est plus nécessaire.

11voto

VGR Points 4236

Il s'avère que vous pouvez le faire, avec l'aide de l'application intégrée Fournisseur du système de fichiers Zip . Cependant, le fait de passer directement un URI de ressource à Paths.get ne fonctionnera pas ; il faut d'abord créer un système de fichiers zip pour l'URI jar sans le nom de l'entrée, puis faire référence à l'entrée dans ce système de fichiers :

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);
}

Mise à jour :

Il a été souligné à juste titre que le code ci-dessus contient une fuite de ressources, puisque le code ouvre un nouvel objet FileSystem mais ne le ferme jamais. La meilleure approche est de passer un objet worker de type Consumer, comme le fait la réponse de Holger. Ouvrez le système de fichiers ZipFS juste assez longtemps pour que le travailleur puisse faire ce qu'il doit faire avec le chemin (tant que le travailleur n'essaie pas de stocker l'objet chemin pour une utilisation ultérieure), puis fermez le système de fichiers.

5voto

Michael Stauffer Points 298

J'ai écrit une petite méthode d'aide pour lire Paths à partir des ressources de votre classe. Il est assez pratique à utiliser car il ne nécessite qu'une référence de la classe dans laquelle vous avez stocké vos ressources ainsi que le nom de la ressource elle-même.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X