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.io
sem 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.nio
que 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; } });