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 Path
APIs sofisticadas com recursos de caminho de classe.
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 Path
APIs sofisticadas com recursos de caminho de classe.
Respostas:
Este funciona para mim:
return Paths.get(ClassLoader.getSystemResource(resourceName).toURI());
Thread.currentThread().getContextClassLoader().getResource(resourceName).toURI()
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();
class.getResource
requer uma barra, mas getSystemResourceAsStream
não consigo encontrar o arquivo quando prefixado com uma barra.
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 file
URIs 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.lang
no exemplo) como Path
s, 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.class
para 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.
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.get
nã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.
newFileSystem
pode 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.
FileSystem
. Se você carregar um recurso de um Jar e criar o necessário FileSystem
- o FileSystem
também permitirá que você carregue outros recursos do mesmo Jar. Além disso, depois de criar o novo, FileSystem
você pode apenas tentar carregar o recurso novamente usando Paths.get(Path)
e a implementação usará automaticamente o novo FileSystem
.
#getPath(String)
método no FileSystem
objeto.
Eu escrevi um pequeno método auxiliar para ler Paths
os 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());
}
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);
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();
}
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)) {
....
}
}
Paths.get(URI)
, então, 'URL.toURI (), and last
getResource () `, que retorna aURL
. Você poderá encadear essas pessoas. Ainda não tentei.