Mostrando entradas con la etiqueta IO. Mostrar todas las entradas
Mostrando entradas con la etiqueta IO. Mostrar todas las entradas

lunes, 21 de febrero de 2011

Código para acomodar imagenes por fecha de modificación

Quize crear un programa pequeño para organizar un montón de fotos que tengo desordenadas en distintas carpetas. En lo particular me gusta tener las fotos almacenadas en carpetas que indiquen la fecha en que se tomaron. De esa manera puedo hacerme una idea cronólogica de cuándo se fueron tomando. Claro está que este código funciona bajo la premisa de que la fecha de modificación de las fotos es la misma de cuándo se tomaron, que es la misma de cuando se descargaron. Puede ser que no sea el caso de muchos archivos, pero al menos ayuda agregando cierto nivel de orden.


Clase Principal:


package gsolano.photoorganizer;

import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* This class is used to copy files from a specific folder (recursive scan in that folder)
* into a new folder structure using modification date of the files to map them correctly.
*
* @author gsolano
*
*/
public class FileOrganizerByModificationDate {

/**
* Extensions to that will be processed.
*/
protected final String [] extensionsToProcess = {"jpg","avi"};
/**
* Year(key): 2010
* ++ Date(key): 20100125
* ++++ File: 010223.jpg
* ++++ File: 232455.avi
*/
protected Map<Integer, Map<String,List<String>>> newFolderStructure;

/**
* Directory path that will be scanned.
*/
protected String pathToProcess;

/**
* Counter of total files found.
*/
protected int totalFilesToOrganize;

/**
* Default constructor.
*/
private FileOrganizerByModificationDate() {
newFolderStructure = new HashMap<Integer,Map<String,List<String>>>();
totalFilesToOrganize = 0;
}

/**
* Constructor receiving path that will be processed.
* @param pathToProcess
*/
public FileOrganizerByModificationDate(String pathToProcess){
this();
this.pathToProcess = pathToProcess;
}

/**
* Scans the path provided in the constructor.
*/
public void scan() {
if(pathToProcess != null && pathToProcess != "") {
scan(pathToProcess);
}
}

/**
* Scans a specific path.
* @param rootDirPath
*/
public void scan(String rootDirPath) {
System.out.println("Scanning: " + rootDirPath);
this.pathToProcess=rootDirPath;
File sourceDir = new File(rootDirPath);

if(sourceDir.exists() && sourceDir.isDirectory()) {
scanRecursively(sourceDir);
System.out.println("Files to organize: " + totalFilesToOrganize);

} else {
System.out.println("Directory does not exist!");
}

if(newFolderStructure.size() > 0) { // Files were added to the new structure?
try {
System.out.println("Copying files...");
copyFiles(); // Copy the files in the new directory structure.
} catch (IOException e) {
e.printStackTrace();
}
// Clean the class..
newFolderStructure.clear();
totalFilesToOrganize = 0;
}
}

/**
* Once scan is finished, this method is in charge of copying files to new structure.
* @throws IOException
*/
private void copyFiles() throws IOException{
Set<Integer> years = newFolderStructure.keySet();
int totalFilesCopied = 0;

for(Integer year : years) {
String newYearDirectoryPath = (pathToProcess.endsWith(File.separator) ? pathToProcess + year.toString() :
pathToProcess+ File.separator + year.toString());
// Let's create the year directory.
File newYearDirectory = new File(newYearDirectoryPath);
newYearDirectory.mkdir();
String newYearAbsoluteDirectoryPath = newYearDirectory.getAbsolutePath();
Set<String> newDateFolders = newFolderStructure.get(year).keySet();

// Diving into the years map.
for(String newDateFolder : newDateFolders) {
// Let's create the date folder.
String newDateFolderPath = newYearAbsoluteDirectoryPath + File.separator + newDateFolder;
File newDateDirectory = new File(newDateFolderPath);
newDateDirectory.mkdir();

List<String> files = newFolderStructure.get(year).get(newDateFolder);

for(String file : files) {
// Finally, copy the file in the respective folder.
FileCopy.copy(file, newDateFolderPath, false);
totalFilesCopied++;
System.out.print("\r"+progress(totalFilesCopied, totalFilesToOrganize));
}
}
}

}

/**
* Recursive method to find empty folders.
* @param sourceDir
*/
private void scanRecursively(File sourceDir) {
// List files and directories in the first level.
File[] listOfFiles = sourceDir.listFiles();

if (listOfFiles != null) {
for(int i=0; i < listOfFiles.length; i++) {
if (listOfFiles[i].isFile()) {
String extension = getExtension(listOfFiles[i].getName());
if (isValidExtension(extension)) {
processFile(listOfFiles[i]);
totalFilesToOrganize++;
}
}
else if(listOfFiles[i].isDirectory()) { // Recursive call.
scanRecursively(listOfFiles[i]);
}
}
}
}

/**
* Process the file found. The file path is added to the new map structure used later to create
* the new directory structure and copy the file.
* @param file
*/
private void processFile(File file) {

DateFormat dfYearOnly = new SimpleDateFormat("yyyy");

Date lastModified = new Date(file.lastModified());
Integer yearOfFile = Integer.valueOf(dfYearOnly.format(lastModified));
DateFormat df = new SimpleDateFormat("yyyyMMdd");
String dateFolder = df.format(lastModified);

if(!newFolderStructure.containsKey(yearOfFile)) {
newFolderStructure.put(yearOfFile, new HashMap<String, List<String>>());
}
if(!newFolderStructure.get(yearOfFile).containsKey(dateFolder)) {
newFolderStructure.get(yearOfFile).put(dateFolder, new LinkedList<String>());
}
newFolderStructure.get(yearOfFile).get(dateFolder).add(file.getAbsolutePath());
}

/**
* Get file's extension *
* @param fileName
* @return file extension
*/
private String getExtension(final String fileName) {
final int x = fileName.lastIndexOf(".");
return (x > 0 && x < fileName.length()) ? fileName.substring(fileName.lastIndexOf(".")+1, fileName.length()) : "" ;
}

/**
* Validates if the extension is valid to be processed.
* @param extension
* @return
*/
private boolean isValidExtension(String extension) {
for(String extensionToProcess : extensionsToProcess) {
if(extensionToProcess.equalsIgnoreCase(extension)){
return true;
}
}
return false;
}

/**
* Simple function to calculate progress of the copy process.
* @param numerator
* @param denominator
* @return
*/
private String progress(int numerator, int denominator) {
float progress = 0;
progress = ((float)numerator / denominator)*100;
DecimalFormat df1 = new DecimalFormat("##.00");
return df1.format(progress)+"%";
}

public static void main(String[] args) {
FileOrganizerByModificationDate organizer =
new FileOrganizerByModificationDate(args[0]);
organizer.scan();
}
}



Clase para hacer el copiado:


package gsolano.photoorganizer;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
* Utility class to copy a file from a source path to a target path.
*
* @author gsolano
*
*/
public class FileCopy {

public static void copy(String fromFileName, String toFileName,
boolean overrideFiles) throws IOException {
File fromFile = new File(fromFileName);
File toFile = new File(toFileName);

if (!fromFile.exists())
throw new IOException("FileCopy: " + "no such source file: "
+ fromFileName);
if (!fromFile.isFile())
throw new IOException("FileCopy: " + "can't copy directory: "
+ fromFileName);
if (!fromFile.canRead())
throw new IOException("FileCopy: " + "source file is unreadable: "
+ fromFileName);

if (toFile.isDirectory())
toFile = new File(toFile, fromFile.getName());
if (toFile.exists()) {
if (!toFile.canWrite()) {
throw new IOException("FileCopy: "
+ "destination file is unwriteable: " + toFileName);
}
if (!overrideFiles) {
// File exist, do not override it.
return;
}

String parent = toFile.getParent();
if (parent == null)
parent = System.getProperty("user.dir");
File dir = new File(parent);
if (!dir.exists())
throw new IOException("FileCopy: "
+ "destination directory doesn't exist: " + parent);
if (dir.isFile())
throw new IOException("FileCopy: "
+ "destination is not a directory: " + parent);
if (!dir.canWrite())
throw new IOException("FileCopy: "
+ "destination directory is unwriteable: " + parent);
} else {
// Create directory.
new File(toFile.getParent()).mkdirs();
}

FileInputStream from = null;
FileOutputStream to = null;
try {
from = new FileInputStream(fromFile);
to = new FileOutputStream(toFile);
byte[] buffer = new byte[4096];
int bytesRead;

while ((bytesRead = from.read(buffer)) != -1)
to.write(buffer, 0, bytesRead); // write
} finally {
if (from != null)
try {
from.close();
} catch (IOException e) {
;
}
if (to != null)
try {
to.close();
toFile.setLastModified(fromFile.lastModified());

} catch (IOException e) {
;
}
}
}
}


Salida:

miércoles, 2 de febrero de 2011

Código para encontrar carpetas vacías

Dejo a la disposición un código corto para encontrar carpetas vacías usando como entrada un path específico. El programa se adentra en él de manera recursiva y guarda al final los resultados en un archivo TXT.


import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.LinkedList;
import java.util.List;

/**
* Class used to scan a given folder recursively to look for all empty folders. *
* @author gsolano
*
*/
public class DirEmptyFolder {

protected List<String> emptyFoldersList; // It will store all the empty folder paths.

public DirEmptyFolder() {
// Initializes the list.
emptyFoldersList = new LinkedList<String>();
}

public void scan(String rootDirPath) {
File sourceDir = new File(rootDirPath);

if(sourceDir.exists() && sourceDir.isDirectory()) {
emptyFoldersList.clear();
/*The real action: scan recursively.*/
scanRecursively(sourceDir);
System.out.println("Found " + emptyFoldersList.size() + " empty folders");

if(emptyFoldersList.size() > 0) {
System.out.println("Saving list...");
writeFilesToDisk("empty_folders_list.txt", emptyFoldersList);
System.out.println("Finished!");
}
} else {
System.out.println("Directory does not exist!");
}
}

/**
* Recursive method to find empty folders.
* @param sourceDir
*/
private void scanRecursively(File sourceDir) {
// List files and directories in the first level.
File[] listOfFiles = sourceDir.listFiles();

if (listOfFiles != null) {
for(int i=0; i < listOfFiles.length; i++) {
if(listOfFiles[i].isDirectory()) { // Only cares about directories.
if(listOfFiles[i].listFiles().length == 0) { // The directory has anything inside?
// Let's store the path and put it on screen.
emptyFoldersList.add(listOfFiles[i].getAbsolutePath());
System.out.println(listOfFiles[i].getAbsolutePath());
}
else {
// It's a directory but is not empty. Let's do the recursive call!
scanRecursively(listOfFiles[i]);
}
}
}
}
}

/**
* Saves the list in disk.
* @param filePath
* @param lines
*/
private void writeFilesToDisk(String filePath, List<String> lines) {
try{
FileWriter fstream = new FileWriter(filePath);
BufferedWriter out = new BufferedWriter(fstream);

for (String line : lines) {
out.write(line + "\n");
}
out.close();
}catch (Exception e){
e.printStackTrace();
}
}

public static void main(String[] args) {
DirEmptyFolder dirEmptyFolder = new DirEmptyFolder();
dirEmptyFolder.scan(args[0]);
}
}



Salida (usando como entrada: "c:\documents and settings\gsolano\My Documents"):
...
c:\documents and settings\gsolano\My Documents\My Meetings
c:\documents and settings\gsolano\My Documents\New Folder
c:\documents and settings\gsolano\My Documents\videos
Found 43 empty folders
Saving list...
Finished!

martes, 20 de julio de 2010

Autenticación en Tomcat para acceso de direcciones de tipo UNC

Estuve lideando con un problema que me tomó un par de días resolver. Tengo un código en Java que utilizó para listar los archivos dentro de una carpeta. Uno simplemente crea una instancia de la clase java.io.File pasando en el constructor la ruta de la carpeta. Después solamente se llama el método "listFiles" y te retorna un arreglo con los archivos encontrados.

La ruta que necesitaba listar era de tipo UNC. La sintaxis utilizada por Microsoft para accesar una localidad de red compartida. Esta ruta estaba protegida con usuario y contraseña en el servidor. No obstante estos ya estaban guardados por windows de manera que no me volvía a preguntar por ellos cada vez que accesaba la dirección de red.

El problema en sí era que la clase de abajo (no es la misma, solo para usos de ejemplificación) funcionaba correctamente si la ejecutaba en línea de comandos. Pero si la usaba en mi aplicación web corriendo en Tomcat, la misma carpeta no se podía encontrar.


import java.io.File;

public class FolderLister {

public File[]listFiles(String address) {
File folder = new File(address);

if(folder.exists()) {
return file.listFiles();
}
return null;
}

public static void main(String [] args) {

FolderLister folderLister = new FolderLister();

File[] listOfFiles = folderLister.listFiles("\\\\remote-host\\path");

for (File f : listOfFiles) {
System.out.println(f.getName());
}
}
}
Intenté probar con el "catalina.policy" pero me di cuenta que nada tenía que ver con mi problema. Después de googlear con las palabras correctas encontré un foro donde indicaban que el Tomcat tenía que ser arrancado con el usuario que tenía acceso a la carpeta compartida. Esto se puede configurar fácilmente en las propiedades del servicio en la pestaña de "Log On".


viernes, 21 de mayo de 2010

Unas cuantas operaciones de archivo || Construyendo un Sincronizador

Recientemente en el proyecto que he estado trabajando en el último par de meses, tuvimos que pensar en una manera de resolver el problema de mantener actualizado unas copias locales de repositorios donde los originales se encuentran en servidores remotos. Debido al tipo de operaciones que necesitamos ejecutar, no podíamos darnos el lujo de hacerlo directamente en los servidores remotos. Si usted está pensando ahora mismo "di mae! Use SVN", bueno, digamos que nuestro cliente no lo tiene y no hay posibilidad cercana de que lo instale por nosotros. Sólo tenemos acceso a las unidades de recurso compartido donde está el sistema de archivos.

Este tipo de operación que requerimos se conoce (al menos así es como lo llamamos), como sincronización de directorios. En nuestro caso es necesario para mantener la versión más actualiza posible de los archivos remotos. La sincronización es muy útil cuando el costo de copiar todo el directorio es muy alto. Por ejemplo, si tenemos un directorio remoto con 200 GB, no sería eficiente copiar todo cada vez que tengamos que actualizar la copia local.

Investigué un poco para encontrar una herramienta que podría hacer lo que quería, y me encontré algunas buenas, pero con el único inconveniente que necesitaba algo que pudiera personalizar a nuestros procesos. Así que empecé a jugar un poco con la clase java.io.File y me di cuenta de que podía programar un sincronizador en poco tiempo.

En este post quiero compartir con ustedes algunas operaciones útiles de la clase File a la luz del problema que mi equipo tenía que resolver.
Pongamos un ejemplo de lo que el sincronizador tiene que hacer. Supongamos que tenemos este directorio remoto:

gsolano_remote
+ 20100514
++ calculations.xls
+ 20100514
++ HelloWorld.java
+ readme.txt


Y tenemos esta copia local que tiene que ser actualizada:

gsolano_local
+ 20100514
++ calculations.xls
++ deletelater
+ bck-ups
+ readme.txt
+ dir.txt


Si comparamos estos 2 directorios, la copia local tendría que ejecutar las siguientes acciones (en paréntesis):

gsolano_local
+ 20100514
++ calculations.xls
++ deletelater (remover)
+ bck-ups (remover)
+ readme.txt (actualizar)
+ dir.txt (remover)
+ 20100514 (agregar)
++ HelloWorld.java (agregar)


La lógica que se necesita codificar para ejecutar estas acciones es bastante simple.
  1. Listamos los archivos del directorio fuente(gsolano_remote).
  2. Listamos los archivos del directorio destino(gsolano_local).
  3. Comparamos los dos listados y extraemos:
  • Lista de nuevos archivos a copiar de la fuente al destino.
  • Lista de archivos que tienen que ser actualizados porque fueron modificados en el directorio fuente.
  • Archivos y directorios que ya no existen en el directorio fuente y que por tanto tienen que ser borrados.
Una vez que obtenemos estas listas solo tenemos que ejecutar las respectivas acciones de copiado y borrado.

Examinemos primero cómo escanear los archivos de un directorio:

package gsolano;

import java.io.File;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;

public class Dir {

/**
* Returns a list of all file paths relative to the provided path.
* @param path
* @return list of relative paths.
*/
public static Map<String, Long> scan(String path) {
Map<String, Long> fileList = new LinkedHashMap<String, Long>();
scanFiles(path.toLowerCase(), path, fileList);
return fileList;
}

/**
* Method for recursively scan.
* @param rootSource
* @param path
* @param fileList
*/
private static void scanFiles(String rootSource, String path, Map<String, Long> fileList) {
File folder = new File(path); // This is the root directory.
// List files from first level of root directory.
File[] listOfFiles = folder.listFiles();

if (listOfFiles.length == 0) {
// Used to keep record of empty folders.
fileList.put(path.toLowerCase().replace(rootSource, "")
+ File.separator + ".", new Long(0));
}
else {
for (int i = 0; i < listOfFiles.length; i++) {
if (listOfFiles[i].isFile()) { // Is it a file?
try {
// Add it to the file list with the last modified date.
fileList.put(listOfFiles[i].getAbsolutePath().toLowerCase()
.replace(rootSource, ""), listOfFiles[i].lastModified());

} catch (Exception e) {
e.printStackTrace();
}
} else if (listOfFiles[i].isDirectory()) { // Is it a directory?
try {
// Recursively call for new found directory.
System.out.println(listOfFiles[i]);
scanFiles(rootSource, listOfFiles[i].getCanonicalPath(), fileList);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

}
}


En este código comenzamos a explorer las capacidades de la clase File. La primera es la habilidad de listar archivos de un directorio. Simplemente creamos una instancia de la clase File con el path de un directorio y luego usamos la función “listFiles()”.

File folder = new File(path);
File[] listOfFiles = folder.listFiles();

Ahora, esta function solamente obtiene los archivos y directories del primer nivel. La función no lista los directorios de los siguiente subniveles; es por eso que en la clase “Dir” el escaneo de archivos trabaja con una función recursiva.

Para determinar si se necesita ejecutar una llamada recursiva, se utiliza la función “isFile()” y “isDirectory()”. Si el archivo que se lee es un directorio (suena extraño ¿cierto?) entonces se hace la llamada recursiva. Si es un archivo se agrega a la lista.

En esta clase usamos también la función “lastModified()” para guardar la última fecha de modificación de cada uno de los archivos escaneados. La fecha se usa para determinar si el archivo de la fuente cambió provocando que se tenga que actualizar en el directorio destino.

Antes de saltar a la clase principal, echémole una mirada a la clase para copiar archivos. Modifiqué un poco esta clase que encontré en Internet :

package gsolano;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileCopy {

/**
* Copies one file from the source to specified target.
* @param fromFileName
* @param toFileName
* @param overrideFiles
* @throws IOException
*/
public static void copy(String fromFileName, String toFileName,
boolean overrideFiles) throws IOException {

File toFile = new File(toFileName);
if (toFile.exists() && !overrideFiles) {
return;
}
File fromFile = new File(fromFileName);

if (!fromFile.exists())
throw new IOException("FileCopy: " + "no such source file: "
+ fromFileName);
if (!fromFile.isFile())
throw new IOException("FileCopy: " + "can't copy directory: "
+ fromFileName);
if (!fromFile.canRead())
throw new IOException("FileCopy: " + "source file is unreadable: "
+ fromFileName);

if (toFile.isDirectory()) {
toFile = new File(toFile, fromFile.getName());
}
if (toFile.exists()) {
if (!toFile.canWrite()) {
throw new IOException("FileCopy: "
+ "destination file is unwriteable: " + toFileName);
}
String parent = toFile.getParent();
if (parent == null)
parent = System.getProperty("user.dir");
File dir = new File(parent);
if (!dir.exists())
throw new IOException("FileCopy: "
+ "destination directory doesn't exist: " + parent);
if (dir.isFile())
throw new IOException("FileCopy: "
+ "destination is not a directory: " + parent);
if (!dir.canWrite())
throw new IOException("FileCopy: "
+ "destination directory is unwriteable: " + parent);
} else {
// Create directory structure.
new File(toFile.getParent()).mkdirs();
}
createCopy(toFile, fromFile);
}

/**
* Writes the copy from source to target.
* @param toFile
* @param fromFile
* @throws FileNotFoundException
* @throws IOException
*/
private static void createCopy(File toFile, File fromFile)
throws FileNotFoundException, IOException {
FileInputStream from = null;
FileOutputStream to = null;
try {
from = new FileInputStream(fromFile);
to = new FileOutputStream(toFile);
byte[] buffer = new byte[4096];
int bytesRead;

while ((bytesRead = from.read(buffer)) != -1)
to.write(buffer, 0, bytesRead); // write
} finally {
if (from != null)
try {
from.close();
} catch (IOException e) {
;
}
if (to != null)
try {
to.close();
toFile.setLastModified(fromFile.lastModified());

} catch (IOException e) {
;
}
}
}
}

La clase FileCopy utilize 6 funciones más de la clase File:

1. “exists()”: usada para doble chequear que el archivo existe en el directorio fuente.

2. “canRead()”: usado para determiner si el archivo puede ser leido del directorio fuente. “canWrite():”: usado para determiner si el archive destino puede ser sobre escribido. Lo usamos cuando queremos actualzar un archivo.

3. “getParent()”: obtiene el path del directorio padre.

4. “mkDirs()”: tengo que confesar que esta es mi favorita; crea toda la jerarquía de directorios de un path.

5. “setLastModifiedDate()”: cuando terminamos de copiar el archivo en el directorio destino, queremos dejar la misma fecha de modificación del archivo fuente.

Para concluir con la clase “Synchronizer” solo tenemos que mencionar una función y una constante:

+ “delete()”: borra el archivo o directorio

+ “File.separator”: character dependiente del sistema usado para separar los directories de un path. En este ejemplo el separador es la barra inclinada hacia atrás o “back-slash” (“\”).

La clase “Synchronizer” hace todos los pasos requeridos para sincronizar 2 directorios. La lista de copias y borrados son calculados con simples comparaciones de colecciones (conjuntos y mapas).

Me gustaría poder probar si el código funciona en otro sistema operativo. Teóricamente sí funciona.


package gsolano;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
*
* Class used to synchronize two directories. One directory (source)
* is used as base of another directory (target).
* The class determines the operations required to leave the target
* with the same structure as the source.
*
* @author gsolano
*
*/
public class Synchronizer {

public static void main(String[] args) {
Synchronizer.run(args[0], args[1]);
}

public static void run(String source, String target) {
System.out.println("Scanning source directory...");
Map<String, Long> sourceFiles = Dir.scan(source);
System.out.println("[DONE]");

System.out.println("Scanning target directory...");
Map<String, Long> targetFiles = Dir.scan(target);
System.out.println("[DONE]");

List<String> newFilesToCopy = getNewFilesToCopy(sourceFiles.keySet(), targetFiles.keySet());
System.out.println("Total new files to copy: " + newFilesToCopy.size());

List<String> filesToUpdate = getFilesToUpdate(sourceFiles, targetFiles);
System.out.println("Total files to update: " + filesToUpdate.size());

List<String> filesToRemove = getFilesToRemove(sourceFiles.keySet(), targetFiles.keySet());
System.out.println("Total files to remove: " + filesToRemove.size());

List<String> dirsToRemove = getDirectoriesToRemove(sourceFiles.keySet(), targetFiles.keySet());
System.out.println("Total dirs to remove: " + dirsToRemove.size());

System.out.println("Copying new files...");
for(String fileToCopy : newFilesToCopy) {
try {
FileCopy.copy(source + File.separator + fileToCopy,
target + File.separator + fileToCopy, false);
} catch (IOException e) {
System.out.println("Couldn't copy file: " + fileToCopy + "(" + e.getMessage() + ")");
}
}

System.out.println("Updating files...");
for(String fileToUpdate : filesToUpdate) {
try {
FileCopy.copy(source + File.separator + fileToUpdate,
target + File.separator +fileToUpdate, true);
} catch (IOException e) {
System.out.println("Couldn't copy file: " + fileToUpdate + "(" + e.getMessage() + ")");
}
}

System.out.println("Removing files from target...");
for(String fileToRemove : filesToRemove) {
new File(target + fileToRemove).delete();
}

System.out.println("Removing directories from target...");
for(String dirToRemove : dirsToRemove) {
new File(target + dirToRemove).delete();
}
}

/**
* Return the list of directories to be removed. A directory is removed
* if it is present in the target but not in the source.
* @param sourceFiles
* @param targetFiles
* @return
*/
private static List<String> getDirectoriesToRemove(Set<String> sourceFiles,
Set<String> targetFiles) {
List<String> directoriesToRemove = new ArrayList<String>();

Set<String> sourceDirs = buildDirectorySet(sourceFiles);
Set<String> targetDirs = buildDirectorySet(targetFiles);

for(String dir : targetDirs) {
if (!sourceDirs.contains(dir)) {
directoriesToRemove.add(dir);
}
}
return directoriesToRemove;
}

/**
* Return the list of files to be removed.
* A file is removed if it is present in the target
* but not in the source.
* @param sourceFiles
* @param targetFiles
* @return
*/
private static List<String> getFilesToRemove(Set<String> sourceFiles,
Set<String> targetFiles) {
List<String> filesToRemove = new ArrayList<String>();

for (String filePath : targetFiles) {
if (!sourceFiles.contains(filePath) &&
!filePath.endsWith(File.separator + ".")) {
filesToRemove.add(filePath);
}
}
return filesToRemove;
}

/**
* Gets the the list of files missing in the target directory.
* @param sourceFiles
* @param targetFiles
* @return
*/
private static List<String> getNewFilesToCopy(Set<String> sourceFiles,
Set<String> targetFiles) {
List<String> filesToCopy = new ArrayList<String>();

for (String filePath : sourceFiles) {
if (!targetFiles.contains(filePath)) {
if(!filePath.endsWith(File.separator + ".")) {
filesToCopy.add(filePath);
}
}
}
return filesToCopy;
}

/**
* Gets the list of files to be updated according to the last
* modified date.
* @param sourceFiles
* @param targetFiles
* @return
*/
private static List<String> getFilesToUpdate(Map<String, Long> sourceFiles,
Map<String, Long> targetFiles) {
List<String> filesToUpdate = new ArrayList<String>();
Iterator<Map.Entry<String, Long>> it = sourceFiles.entrySet().iterator();

while (it.hasNext()) {
Map.Entry<String, Long> pairs = it.next();
String filePath = pairs.getKey();
if (targetFiles.containsKey(filePath) &&
!filePath.endsWith(File.separator + ".")) {
long sourceModifiedDate = sourceFiles.get(filePath);
long targetModifiedDate = targetFiles.get(filePath);

if(sourceModifiedDate != targetModifiedDate) {
filesToUpdate.add(filePath);
}
}
}
return filesToUpdate;
}

/**
* Returns the set of directories contained in the set of file paths.
* @param files
* @return Set of directories representing the directory structure.
*/
private static Set<String> buildDirectorySet(Set<String> files) {
Set<String> directories = new HashSet<String>();
for(String filePath : files) {
if (filePath.contains(File.separator)) {
directories.add(filePath.substring(0,
filePath.lastIndexOf(File.separator)));
}
}
return directories;
}
}

Salida: