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.
Réponses
Trop de publicités?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();
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.
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.
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());
}
- Réponses précédentes
- Plus de réponses