web-dev-qa-db-fra.com

java.nio.file.Path pour une ressource de chemin de classe

Existe-t-il une API permettant d’obtenir une ressource de chemin de classe (par exemple, ce qui serait obtenu de Class.getResource(String) ) en tant que Java.nio.file.Path Idéalement, j'aimerais utiliser les nouvelles API fantaisie avec les ressources classpath.

110
Louis Wasserman

Celui-ci fonctionne pour moi:

return Paths.get(ClassLoader.getSystemResource(resourceName).toURI());
121
keyoxy

Si vous devinez bien ce que vous voulez faire, appelez Files.lines (...) sur une ressource provenant du chemin de classe, éventuellement à l'intérieur d'un fichier jar.

Depuis qu'Oracle a compliqué la notion de lorsqu'un chemin est un chemin en ne faisant pas en sorte que getResource renvoie un chemin utilisable s'il réside dans un fichier JAR, vous devez procéder comme suit:

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

Il s'avère que vous pouvez le faire, avec l'aide du fournisseur de système de fichiers intégré Zip . Cependant, passer un URI de ressource directement à Paths.get ne fonctionnera pas; au lieu de cela, il faut d'abord créer un système de fichiers Zip pour l'URI de jar sans le nom de l'entrée, puis se référer à l'entrée de 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);
}

Mettre à jour:

Il a été souligné à juste titre que le code ci-dessus contient une fuite de ressource, car le code ouvre un nouvel objet FileSystem mais ne le ferme jamais. La meilleure approche consiste à transmettre un objet travailleur de type consommateur, un peu 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 qu’il n’essaye pas de stocker l’objet Path pour une utilisation ultérieure), puis fermez le système de fichiers.

9
VGR

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

Le principal obstacle est de traiter les deux possibilités, soit avoir un système de fichiers existant que nous devrions utiliser, mais pas fermer (comme avec file URI ou le stockage de module de Java 9), soit devoir ouvrir et fermer ainsi le système de fichiers nous-mêmes ( comme les fichiers Zip/jar).

Par conséquent, la solution ci-dessus encapsule l'action réelle dans une interface, gère les deux cas, se ferme en toute sécurité par la suite dans le deuxième cas et fonctionne de Java 7 à Java 10. Elle vérifie s'il existe déjà un système de fichiers ouvert avant d'en ouvrir un nouveau. cela fonctionne é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 lister le contenu d'un paquet (Java.lang dans l'exemple) comme Paths, 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éthodes 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);
    }
});

faire la même chose.


La version finale du système de modules de Java 9 a cassé l’exemple de code ci-dessus. Le JRE renvoie de manière incohérente le chemin /Java.base/Java/lang/Object.class pour Object.class.getResource("Object.class") alors qu'il devrait être /modules/Java.base/Java/lang/Object.class. Cela peut être corrigé en ajoutant le /modules/ manquant 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.

9
Holger

J'ai écrit une petite méthode d'assistance pour lire Paths à partir de vos ressources de classe. Il est très pratique à utiliser car il ne nécessite que la 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());
}  
3
Michael Stauffer

Vous ne pouvez pas créer d'URI à partir de ressources à l'intérieur du fichier jar. Vous pouvez simplement l'écrire dans le fichier temporaire, puis l'utiliser (Java8):

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

Vous devez définir le système de fichiers pour lire la ressource à partir du fichier jar comme indiqué dans https://docs.Oracle.com/javase/8/docs/technotes/guides/io/fsp/zipfilesystemprovider.html . J'ai réussi à lire la ressource du fichier jar avec les codes ci-dessous:

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)) {
            ....
        }
    }
0
user3050291

Lire un fichier depuis le dossier des ressources à l'aide de NIO, en 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();
    }
0
Real Coder