com.hfg.util.FileTreeSnapshot Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com_hfg Show documentation
Show all versions of com_hfg Show documentation
com.hfg xml, html, svg, and bioinformatics utility library
package com.hfg.util;
import java.io.File;
import java.io.FileFilter;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import com.hfg.util.collection.CollectionUtil;
import com.hfg.xml.XMLNode;
import com.hfg.xml.XMLTag;
//------------------------------------------------------------------------------
/**
* Identifies files that change between two points in time.
* @author J. Alex Taylor, hairyfatguy.com
*/
//------------------------------------------------------------------------------
// com.hfg XML/HTML Coding Library
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
// [email protected]
//------------------------------------------------------------------------------
public class FileTreeSnapshot
{
private String mRootPath;
private Long mSnapshotTimestamp;
private Map mFileTimestampMap;
private int mMaxDepth = -1;
private FileFilter mFilter;
//##########################################################################
// CONSTRUCTORS
//##########################################################################
//--------------------------------------------------------------------------
public FileTreeSnapshot(File inRoot)
{
this(inRoot, null);
}
//--------------------------------------------------------------------------
public FileTreeSnapshot(File inRoot, int inMaxDepth)
{
this(inRoot, null, inMaxDepth);
}
//--------------------------------------------------------------------------
public FileTreeSnapshot(File inRoot, FileFilter inFilter)
{
this(inRoot, inFilter, -1);
}
//--------------------------------------------------------------------------
public FileTreeSnapshot(File inRoot, FileFilter inFilter, int inMaxDepth)
{
mFilter = inFilter;
mMaxDepth = inMaxDepth;
mFileTimestampMap = new HashMap<>();
mRootPath = inRoot.getPath() + (inRoot.isDirectory() ? "/" : "");
mSnapshotTimestamp = System.currentTimeMillis();
recursivelyExtract(inRoot, 0);
}
//--------------------------------------------------------------------------
public FileTreeSnapshot(XMLNode inXMLNode)
{
this(inXMLNode, null);
}
//--------------------------------------------------------------------------
public FileTreeSnapshot(XMLNode inXMLNode, FileFilter inFilter)
{
mFilter = inFilter;
if (! inXMLNode.getTagName().equalsIgnoreCase("FileTreeSnapshot"))
{
throw new RuntimeException("Cannont create a " + this.getClass().getSimpleName() + " from an " + inXMLNode.getTagName() + " tag!");
}
mRootPath = inXMLNode.getAttributeValue("root");
String timestampString = inXMLNode.getAttributeValue("timestamp");
if (StringUtil.isSet(timestampString))
{
mSnapshotTimestamp = Long.parseLong(timestampString);
}
mFileTimestampMap = new HashMap<>();
for (XMLNode subtag : inXMLNode.getXMLNodeSubtags())
{
mFileTimestampMap.put(subtag.getAttributeValue("path"), Long.parseLong(subtag.getAttributeValue("lastModified")));
}
}
//##########################################################################
// PUBLIC METHODS
//##########################################################################
//--------------------------------------------------------------------------
public XMLNode toXMLNode()
{
XMLTag rootTag = new XMLTag("FileTreeSnapshot");
rootTag.setAttribute("root", mRootPath);
rootTag.setAttribute("timestamp", mSnapshotTimestamp);
for (String path : mFileTimestampMap.keySet())
{
XMLTag fileTag = new XMLTag("File");
fileTag.setAttribute("path", path);
fileTag.setAttribute("lastModified", mFileTimestampMap.get(path));
rootTag.addSubtag(fileTag);
}
return rootTag;
}
//--------------------------------------------------------------------------
public Date getSnapshotDate()
{
return (mSnapshotTimestamp != null ? new Date(mSnapshotTimestamp) : null);
}
//--------------------------------------------------------------------------
public List getFiles()
{
List files = new ArrayList<>(mFileTimestampMap.size());
for (String path : mFileTimestampMap.keySet())
{
files.add(new File(path));
}
return files;
}
//--------------------------------------------------------------------------
/**
Returns a list of files that are present in the specified file root but not
in the snapshot or files that are present in both the specified file root
and the snapshot but whose last modification dates are different.
* @param inRoot
*/
public List getModifiedFiles(File inRoot)
{
List changedFiles = new ArrayList<>();
recursivelyCompare(inRoot, changedFiles, inRoot.getPath() + (inRoot.isDirectory() ? "/" : ""), 0);
return changedFiles;
}
//--------------------------------------------------------------------------
/**
Returns a list of create/modify/delete events for files changed relative to the snapshot.
* @param inRoot file root to compare with the snapshot
*/
public List> getEvents(File inRoot)
{
List> events = new ArrayList<>();
Map fileMapCopy = new HashMap<>(mFileTimestampMap);
recursivelyExtractEvents(inRoot, events, inRoot.getPath() + (inRoot.isDirectory() ? "/" : ""), fileMapCopy, 0);
// Any entries remaining in the copy of the file map must have been deleted since the snapshot
if (CollectionUtil.hasValues(fileMapCopy))
{
for (String filePath : fileMapCopy.keySet())
{
events.add(new FileEvent(StandardWatchEventKinds.ENTRY_DELETE, new File(inRoot, filePath).toPath()));
}
}
return events;
}
//##########################################################################
// PRIVATE METHODS
//##########################################################################
//--------------------------------------------------------------------------
private void recursivelyExtract(File inFile, int inCurrentDepth)
{
if (inFile.isDirectory())
{
if (mMaxDepth <= 0 || inCurrentDepth < mMaxDepth)
{
for (File file : inFile.listFiles())
{
recursivelyExtract(file, inCurrentDepth + 1);
}
}
}
else if (null == mFilter
|| mFilter.accept(inFile))
{
String rootStrippedPath = inFile.getPath().substring(inFile.getPath().indexOf(mRootPath) + mRootPath.length());
mFileTimestampMap.put(rootStrippedPath, inFile.lastModified());
}
}
//--------------------------------------------------------------------------
private void recursivelyCompare(File inFile, List inChangedFiles, String inRootPath, int inCurrentDepth)
{
if (inFile.isDirectory())
{
if (mMaxDepth <= 0 || inCurrentDepth < mMaxDepth)
{
for (File file : inFile.listFiles())
{
recursivelyCompare(file, inChangedFiles, inRootPath, inCurrentDepth + 1);
}
}
}
else if (null == mFilter
|| mFilter.accept(inFile))
{
String rootStrippedPath = inFile.getPath().substring(inFile.getPath().indexOf(inRootPath) + inRootPath.length());
Long snapshotLastModifiedTime = mFileTimestampMap.get(rootStrippedPath);
if (null == snapshotLastModifiedTime
|| snapshotLastModifiedTime != inFile.lastModified())
{
inChangedFiles.add(inFile);
}
}
}
//--------------------------------------------------------------------------
private void recursivelyExtractEvents(File inFile, List> inEvents, String inRootPath, Map inPrevFileMap, int inCurrentDepth)
{
String rootStrippedPath = inFile.getPath().substring(inFile.getPath().indexOf(inRootPath) + inRootPath.length());
Long snapshotLastModifiedTime = inPrevFileMap.remove(rootStrippedPath);
if (inFile.isDirectory())
{
if (mMaxDepth <= 0 || inCurrentDepth < mMaxDepth)
{
for (File file : inFile.listFiles())
{
recursivelyExtractEvents(file, inEvents, inRootPath, inPrevFileMap, inCurrentDepth + 1);
}
}
}
else if (null == mFilter
|| mFilter.accept(inFile))
{
if (null == snapshotLastModifiedTime)
{
inEvents.add(new FileEvent(StandardWatchEventKinds.ENTRY_CREATE, inFile.toPath()));
}
else if (snapshotLastModifiedTime != inFile.lastModified())
{
inEvents.add(new FileEvent(StandardWatchEventKinds.ENTRY_MODIFY, inFile.toPath()));
}
}
}
//##########################################################################
// PRIVATE CLASS
//##########################################################################
private class FileEvent implements WatchEvent
{
private Path mPath;
private WatchEvent.Kind mKind;
//-----------------------------------------------------------------------
public FileEvent(WatchEvent.Kind inEventType, Path inPath)
{
mKind = inEventType;
mPath = inPath;
}
//-----------------------------------------------------------------------
@Override
public String toString()
{
return mKind.name() + ": " + mPath;
}
//-----------------------------------------------------------------------
public WatchEvent.Kind kind()
{
return mKind;
}
//-----------------------------------------------------------------------
public int count()
{
return 1;
}
//-----------------------------------------------------------------------
public Path context()
{
return mPath;
}
}
}