Vamos dizer que temos um diretório chamado arquivos com o seguinte conteúdo:
.
└── arquivos
├── .bookignore
├── book.properties
├── imgs
└── cover.jpg
└── .gitignore
Queremos copiar todos os arquivos bem como o conteúdo do sub-diretório imgs para outro diretório chamado copia.
Fazer isso é fácil com os novos recursos do pacote java.nio disponíveis a partir do Java 7. Essas novidades foram definidas na JSR-203 e ficaram conhecidas como NIO.2.
Até o Java 1.3, havia somente o pacote
java.iosem suporte adequado a abstrações de alto nível como channel, apenas com I/O bloqueante, suporte ruim a character encodings.O Java 1.4 veio com o pacote
java.nioque resolvia várias dessas limitações.Com o NIO.2 do Java 7, foi simplificada a API do pacote
java.nio, dado suporte a I/O assíncrono, criada uma abstração de sistema de arquivos, entre outras melhorias.
Para representar um diretório, devemos usar a classe Path, obtendo instâncias a partir da classe auxiliar Paths.
Path origem = Paths.get("./arquivos");
Path destino = Paths.get("./copia");
Para navegar no diretório de origem de forma a incluir sub-diretórios (ou seja, recursivamente) podemos usar o método estático walkFileTree da classe auxiliar Files. Precisamos passar um objeto que implementa a interface FileVisitor.
Files.walkFileTree(origem, new Copiador(origem, destino));
A classe Copiador define todos os métodos exigidos pela interface FileVisitor:
preVisitDirectory– chamado antes de visitar um novo sub-diretóriopostVisitDirectory– chamado depois de visitar um sub-diretório e todos seus descendentesvisitFile– chamado ao visitar cada novo arquivovisitFileFailed– chamando quando há falha em algum arquivo
Em cada método, devemos retornar um valor da enum FileVisitResult. Retornaremos FileVisitResult.CONTINUE, para continuar a navegação recursiva nos sub-diretórios.
class Copiador implements FileVisitor<Path> {
private Path origem;
private Path destino;
public Copiador(Path origem, Path destino) {
this.origem = origem;
this.destino = destino;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
//copia arquivo para destino...
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
throw exc;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
return FileVisitResult.CONTINUE;
}
}
Ainda não preenchemos a lógica de copiar os arquivos, que deveria ficar no método visitFile.
Antes disso, podemos simplificar um pouco o código herdando da classe auxiliar SimpleFileVisitor e sobre-escrevendo apenas o método necessário.
class Copiador extends SimpleFileVisitor<Path> {
private Path origem;
private Path destino;
public Copiador(Path origem, Path destino) {
this.origem = origem;
this.destino = destino;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
//copia arquivo para destino...
return FileVisitResult.CONTINUE;
}
}
No método visitFile, vamos pegar o caminho absoluto do arquivo que está sendo visitado transformando-o em um caminho relativo em relação ao diretório de origem.
Então, podemos obter o caminho do mesmo arquivo mas relativo ao destino.
Precisamos criar qualquer diretório “pai” intermediário necessário para o arquivo de destino, através do método estático createDirectories da classe auxiliar Files.
Aí, basta invocar o método estático copy de Files para efetuar a cópia.
No caso de algum diretório ou arquivo já existir, é lançada a exceção FileAlreadyExistsException, que vamos ignorar.
Teremos o código:
Path caminhoAbsoluto = file.toAbsolutePath();
Path caminhoRelativo = origem.relativize(caminhoAbsoluto);
Path arquivoDestino = destino.resolve(caminhoRelativo.toString());
try {
Files.createDirectories(arquivoDestino);
Files.copy(file, arquivoDestino);
} catch (FileAlreadyExistsException ex) {
//se o diretorio ou arquivo ja existir, nao faz nada (nem sobreescreve)
}
Se quisermos sobre-escrever arquivos já existentes, basta passar o valor StandardCopyOption.REPLACE_EXISTING para o método copy.
Files.copy(file, target, StandardCopyOption.REPLACE_EXISTING);
Poderíamos utilizar uma classe anônima ao invés de ter uma classe que herda de SimpleFileVisitor. Juntando tudo, teríamos:
final Path origem = Paths.get("./src");
final Path destino = Paths.get("./copia");
Files.walkFileTree(origem, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Path caminhoAbsoluto = file.toAbsolutePath();
Path caminhoRelativo = origem.relativize(caminhoAbsoluto);
Path arquivoDestino = destino.resolve(caminhoRelativo.toString());
try {
Files.createDirectories(arquivoDestino);
} catch (FileAlreadyExistsException ex) {
//se o diretorio ja existir, nao faz nada (nem sobreescreve)
}
Files.copy(file, arquivoDestino, StandardCopyOption.REPLACE_EXISTING);
return FileVisitResult.CONTINUE;
}
});
excelente post, um pouco babuncado a explicacao kkkk, mas esta otimo!! vlw