net.sf.javagimmicks.io.folderdiff.FolderDiffBuilder Maven / Gradle / Ivy
Show all versions of gimmicks-io Show documentation
package net.sf.javagimmicks.io.folderdiff;
import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import net.sf.javagimmicks.collections.CollectionDifference;
import net.sf.javagimmicks.collections.diff.Difference;
import net.sf.javagimmicks.collections.diff.DifferenceList;
import net.sf.javagimmicks.collections.diff.DifferenceUtils;
import net.sf.javagimmicks.collections.transformer.TransformerUtils;
import net.sf.javagimmicks.event.EventListener;
import net.sf.javagimmicks.event.ObservableBase;
import net.sf.javagimmicks.io.folderdiff.FileInfo.Origin;
import net.sf.javagimmicks.util.Function;
/**
* This class is the starting point for comparing two folders - it allows to
* configure and finally execute the folder comparison returning a
* {@link FolderDiff} object which wraps the results.
*
* Folder comparison always needs a source and target folder specification and
* can be performed {@link #setRecursive(boolean) recursively} or not.
*
* It is also configurable how {@link File} contents are to be compared. Clients
* can activate any of the following modes (all are off by default):
*
* - {@link #setCompareSize(boolean) File size comparison}
* - {@link #setCompareLastModified(boolean) Change date comparison}
* - {@link #setCompareChecksum(boolean) File checksum comparison}
*
* Note that comparison strategies are applied in the shown order using a
* first-hit algorithm (e.g. checksum is not compared if size already
* differs).
*
* Finally it is possible to filter compared files/folders per side via
* includes/excludes (following Ant style).
*/
public class FolderDiffBuilder extends ObservableBase
{
private final Comparator PATH_COMPARATOR = FileInfoComparatorBuilder.PATH_INFO_COMPARATOR;
private final File _sourceFolder;
private final Collection _sourceIncludes = new TreeSet();
private final Collection _sourceExcludes = new TreeSet();
private final File _targetFolder;
private final Collection _targetIncludes = new TreeSet();
private final Collection _targetExcludes = new TreeSet();
private final FileInfoComparatorBuilder _comparatorBuilder = new FileInfoComparatorBuilder(this);
private boolean _recursive;
/**
* Creates a new instance for the given {@link File source folder} and
* {@link File target folder} using recursion depending on the given flag.
*
* @param sourceFolder
* the {@link File source folder} of the comparison
* @param targetFolder
* the {@link File target folder} of the comparison
* @param recursive
* if comparison should be done recursively
*/
public FolderDiffBuilder(final File sourceFolder, final File targetFolder, final boolean recursive)
{
_sourceFolder = sourceFolder;
_targetFolder = targetFolder;
_recursive = recursive;
}
/**
* Creates a new instance for the given {@link File source folder} and
* {@link File target folder} using recursive scanning.
*
* @param sourceFolder
* the {@link File source folder} of the comparison
* @param targetFolder
* the {@link File target folder} of the comparison
*/
public FolderDiffBuilder(final File sourceFolder, final File targetFolder)
{
this(sourceFolder, targetFolder, true);
}
/**
* Starts a new comparison run and wraps the results into a
* {@link FolderDiff} object.
*
* Attention: this operation is not Thread-safe! Client should take
* care about proper synchronization!
*
* @return the {@link FolderDiff} object containing the results of the
* comparison
*/
public FolderDiff buildFolderDiff()
{
final IncludeExcludeFilenameFilter sourceFilter = new IncludeExcludeFilenameFilter(_sourceIncludes,
_sourceExcludes);
final FileScanner sourceScanner = new FileScanner(Origin.Source, this, _sourceFolder, sourceFilter, _recursive);
final List sourceFiles = sourceScanner.scan();
final IncludeExcludeFilenameFilter targetFilter = new IncludeExcludeFilenameFilter(_targetIncludes,
_targetExcludes);
final FileScanner targetScanner = new FileScanner(Origin.Target, this, _targetFolder, targetFilter, _recursive);
final List targetFiles = targetScanner.scan();
final SortedSet filesAll = new TreeSet(PATH_COMPARATOR);
filesAll.addAll(getPathInfoCollection(sourceFiles));
filesAll.addAll(getPathInfoCollection(targetFiles));
final DifferenceList differences = DifferenceUtils.findDifferences(sourceFiles, targetFiles,
_comparatorBuilder.buildComparator());
final SortedSet filesDifferent = new TreeSet(PATH_COMPARATOR);
final SortedSet filesSourceOnly = new TreeSet(PATH_COMPARATOR);
final SortedSet filesTargetOnly = new TreeSet(PATH_COMPARATOR);
for (final Difference difference : differences)
{
final List listDelete = difference.deleteRange();
final List listAdd = difference.addRange();
final CollectionDifference collectionDifference = CollectionDifference.create(listDelete,
listAdd);
filesDifferent.addAll(getPathInfoCollection(collectionDifference.getBoth()));
filesSourceOnly.addAll(getPathInfoCollection(collectionDifference.getOnlyA()));
filesTargetOnly.addAll(getPathInfoCollection(collectionDifference.getOnlyB()));
}
final SortedSet filesEqual = new TreeSet(filesAll);
filesEqual.removeAll(filesDifferent);
filesEqual.removeAll(filesSourceOnly);
filesEqual.removeAll(filesTargetOnly);
return new FolderDiff(
_sourceFolder, _targetFolder,
filesAll, filesEqual, filesDifferent, filesSourceOnly, filesTargetOnly);
}
/**
* Returns the {@link File source folder} of the comparison
*
* @return the {@link File source folder} of the comparison
*/
public File getSourceFolder()
{
return _sourceFolder;
}
/**
* Returns the included names of files/folders to scan on the source side
*
* @return the included names of files/folders to scan on the source side
*/
public Collection getSourceIncludes()
{
return _sourceIncludes;
}
/**
* Returns the excluded names of files/folders not to scan on the source side
*
* @return the excluded names of files/folders not to scan on the source side
*/
public Collection getSourceExcludes()
{
return _sourceExcludes;
}
/**
* Returns the {@link File target folder} of the comparison
*
* @return the {@link File target folder} of the comparison
*/
public File getTargetFolder()
{
return _targetFolder;
}
/**
* Returns the included names of files/folders to scan on the target side
*
* @return the included names of files/folders to scan on the target side
*/
public Collection getTargetIncludes()
{
return _targetIncludes;
}
/**
* Returns the excluded names of files/folders not to scan on the target side
*
* @return the excluded names of files/folders not to scan on the target side
*/
public Collection getTargetExcludes()
{
return _targetExcludes;
}
/**
* Returns if comparisons are run in recursive mode.
*
* @return if comparisons are run in recursive mode
*/
public boolean isRecursive()
{
return _recursive;
}
/**
* Returns if {@link File}s should be compared via checksum.
*
* @return if {@link File}s should be compared via checksum
*/
public boolean isCompareChecksum()
{
return _comparatorBuilder.isCompareChecksum();
}
/**
* Returns if {@link File}s should be compared via last modified date.
*
* @return if {@link File}s should be compared via last modified date
*/
public boolean isCompareLastModified()
{
return _comparatorBuilder.isCompareLastModified();
}
/**
* Returns if {@link File}s should be compared via size.
*
* @return if {@link File}s should be compared via size
*/
public boolean isCompareSize()
{
return _comparatorBuilder.isCompareSize();
}
/**
* Adds new inclusion pattern(s) for the source folder
*
* @param patterns
* the pattern {@link String}s to add
* @return the {@link FolderDiffBuilder} itself
*/
public FolderDiffBuilder addSourceIncludes(final String... patterns)
{
return addSourceIncludes(Arrays.asList(patterns));
}
/**
* Adds new exclusion patterns for the source folder
*
* @param patterns
* the pattern {@link String}s to add
* @return the {@link FolderDiffBuilder} itself
*/
public FolderDiffBuilder addSourceExcludes(final String... patterns)
{
return addSourceExcludes(Arrays.asList(patterns));
}
/**
* Adds new inclusion pattern(s) for the target folder
*
* @param patterns
* the pattern {@link String}s to add
* @return the {@link FolderDiffBuilder} itself
*/
public FolderDiffBuilder addTargetIncludes(final String... patterns)
{
return addTargetIncludes(Arrays.asList(patterns));
}
/**
* Adds new exclusion pattern(s) for the target folder
*
* @param patterns
* the pattern {@link String}s to add
* @return the {@link FolderDiffBuilder} itself
*/
public FolderDiffBuilder addTargetExcludes(final String... patterns)
{
return addTargetExcludes(Arrays.asList(patterns));
}
/**
* Adds new inclusion pattern(s) for the source folder
*
* @param patterns
* the pattern {@link String}s to add
* @return the {@link FolderDiffBuilder} itself
*/
public FolderDiffBuilder addSourceIncludes(final Collection patterns)
{
_sourceIncludes.addAll(patterns);
return this;
}
/**
* Adds new exclusion pattern(s) for the source folder
*
* @param patterns
* the pattern {@link String}s to add
* @return the {@link FolderDiffBuilder} itself
*/
public FolderDiffBuilder addSourceExcludes(final Collection patterns)
{
_sourceExcludes.addAll(patterns);
return this;
}
/**
* Adds new inclusion pattern(s) for the target folder
*
* @param patterns
* the pattern {@link String}s to add
* @return the {@link FolderDiffBuilder} itself
*/
public FolderDiffBuilder addTargetIncludes(final Collection patterns)
{
_targetIncludes.addAll(patterns);
return this;
}
/**
* Adds new exclusion pattern(s) for the target folder
*
* @param patterns
* the pattern {@link String}s to add
* @return the {@link FolderDiffBuilder} itself
*/
public FolderDiffBuilder addTargetExcludes(final Collection patterns)
{
_targetExcludes.addAll(patterns);
return this;
}
/**
* Enables or disables recursive scanning.
*
* @param recursive
* if recursive scanning should be enabled or disabled
* @return the {@link FolderDiffBuilder} itself
*/
public FolderDiffBuilder setRecursive(final boolean recursive)
{
_recursive = recursive;
return this;
}
/**
* Enables or disables {@link File} checksum comparison.
*
* @param compareChecksum
* if {@link File} checksum comparison should be enabled or
* disabled
* @return the {@link FolderDiffBuilder} itself
*/
public FolderDiffBuilder setCompareChecksum(final boolean compareChecksum)
{
_comparatorBuilder.setCompareChecksum(compareChecksum);
return this;
}
/**
* Enables or disables {@link File} change date comparison.
*
* @param compareLastModified
* if {@link File} change date comparison should be enabled or
* disabled
* @return the {@link FolderDiffBuilder} itself
*/
public FolderDiffBuilder setCompareLastModified(final boolean compareLastModified)
{
_comparatorBuilder.setCompareLastModified(compareLastModified);
return this;
}
/**
* Enables or disables {@link File} size comparison.
*
* @param compareSize
* if {@link File} change size should be enabled or disabled
* @return the {@link FolderDiffBuilder} itself
*/
public FolderDiffBuilder setCompareSize(final boolean compareSize)
{
_comparatorBuilder.setCompareSize(compareSize);
return this;
}
/**
* Convenience method for {@link #addEventListener(EventListener)} that
* additionally return the current {@link FolderDiffBuilder} so that the
* method is usable for fluent building.
*
* @param listener
* the {@link EventListener} to add
* @return the {@link FolderDiffBuilder} itself
*/
public FolderDiffBuilder addListener(final EventListener listener)
{
addEventListener(listener);
return this;
}
/**
* Reset this instance to default values:
*
* - Clears all include/exclude filters on source and target side
* - Disables all three file comparison options (file will only be compared
* by existence)
* - Enables recursion
*
*
* @return the {@link FolderDiffBuilder} itself
*/
public FolderDiffBuilder reset()
{
_sourceIncludes.clear();
_sourceExcludes.clear();
_targetIncludes.clear();
_targetExcludes.clear();
setCompareChecksum(false);
setCompareLastModified(false);
setCompareSize(false);
setRecursive(true);
return this;
}
private static Collection getPathInfoCollection(final Collection collection)
{
return TransformerUtils.decorate(collection, FILE_TO_PATH_INFO);
}
private static final Function FILE_TO_PATH_INFO = new Function()
{
@Override
public PathInfo apply(final FileInfo source)
{
return source.getPathInfo();
}
};
}