Copiando arquivos de um diretório e seus sub-diretórios com Java 7+

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ório
  • postVisitDirectory – chamado depois de visitar um sub-diretório e todos seus descendentes
  • visitFile – chamado ao visitar cada novo arquivo
  • visitFileFailed – 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;
  }
});
Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s