com.hfg.util.io.FileObjTreeSnapshot 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.io;
import java.net.URI;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import com.hfg.util.CompareUtil;
import com.hfg.util.StringUtil;
import com.hfg.util.collection.CollectionUtil;
import com.hfg.xml.XMLNode;
import com.hfg.xml.XMLTag;
//------------------------------------------------------------------------------
/**
* Serializable snapshot that 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 FileObjTreeSnapshot
{
private FileObj mRoot = null;
private final String mRootPath;
private Long mSnapshotTimestamp;
private final Map mFileTimestampMap;
private int mMaxDepth = -1;
private final FileObjFilter mFilter;
//##########################################################################
// CONSTRUCTORS
//##########################################################################
//--------------------------------------------------------------------------
public FileObjTreeSnapshot(FileObj inRoot)
{
this(inRoot, null);
}
//--------------------------------------------------------------------------
public FileObjTreeSnapshot(FileObj inRoot, int inMaxDepth)
{
this(inRoot, null, inMaxDepth);
}
//--------------------------------------------------------------------------
public FileObjTreeSnapshot(FileObj inRoot, FileObjFilter inFilter)
{
this(inRoot, inFilter, -1);
}
//--------------------------------------------------------------------------
public FileObjTreeSnapshot(FileObj inRoot, FileObjFilter inFilter, int inMaxDepth)
{
mRoot = inRoot;
mRootPath = inRoot.getPath() + (! inRoot.getPath().endsWith("/") && inRoot.isDirectory() ? "/" : "");
mFilter = inFilter;
mMaxDepth = inMaxDepth;
mFileTimestampMap = new HashMap<>();
mSnapshotTimestamp = System.currentTimeMillis();
recursivelyExtract(inRoot, 0);
}
//--------------------------------------------------------------------------
public FileObjTreeSnapshot(XMLNode inXMLNode)
{
this(inXMLNode, null);
}
//--------------------------------------------------------------------------
public FileObjTreeSnapshot(XMLNode inXMLNode, FileObjFilter inFilter)
{
mFilter = inFilter;
if (! inXMLNode.getTagName().equalsIgnoreCase("FileTreeSnapshot"))
{
throw new RuntimeException("Cannot 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())
{
try
{
FileObj file = FileObj.allocateFileObj(new URI(subtag.getAttributeValue("uri")));
String relativePath = subtag.getAttributeValue("path");
if (subtag.hasAttribute("lastModified"))
{
// This attribute will be used as an override for the lastModified() method.
file.setAttribute(FileObj.LAST_MODIFIED_ATTR, Long.parseLong(subtag.getAttributeValue("lastModified")));
}
mFileTimestampMap.put(relativePath, file);
}
catch (Exception e)
{
throw new RuntimeIOException(e);
}
}
}
//##########################################################################
// PUBLIC METHODS
//##########################################################################
//--------------------------------------------------------------------------
public XMLNode toXMLNode()
{
XMLTag rootTag = new XMLTag("FileTreeSnapshot");
rootTag.setAttribute("root", mRootPath);
rootTag.setAttribute("timestamp", mSnapshotTimestamp);
for (String relativePath : mFileTimestampMap.keySet())
{
FileObj file = mFileTimestampMap.get(relativePath);
XMLTag fileTag = new XMLTag("File");
fileTag.setAttribute("path", relativePath);
fileTag.setAttribute("uri", file.getURI().toString());
if (file.lastModified() != null)
{
fileTag.setAttribute("lastModified", file.lastModified());
}
rootTag.addSubtag(fileTag);
}
return rootTag;
}
//--------------------------------------------------------------------------
public Date getSnapshotDate()
{
return (mSnapshotTimestamp != null ? new Date(mSnapshotTimestamp) : null);
}
//--------------------------------------------------------------------------
public List getFiles()
{
return new ArrayList<>(mFileTimestampMap.values());
}
//--------------------------------------------------------------------------
/**
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(FileObj inRoot)
{
List changedFiles = new ArrayList<>(25);
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(FileObj inRoot)
{
List events = new ArrayList<>(25);
Map fileMapCopy = new HashMap<>(mFileTimestampMap);
recursivelyExtractEvents(inRoot, events, inRoot.getPath(), fileMapCopy, 0);
// Any entries remaining in the copy of the file map must have been deleted since the snapshot
if (CollectionUtil.hasValues(fileMapCopy))
{
for (FileObj file : fileMapCopy.values())
{
events.add(new FileObjEvent(file, FileEventType.DELETED));
}
}
return events;
}
//##########################################################################
// PRIVATE METHODS
//##########################################################################
//--------------------------------------------------------------------------
private void recursivelyExtract(FileObj inFile, int inCurrentDepth)
{
if (inFile.isDirectory())
{
if (mMaxDepth <= 0 || inCurrentDepth < mMaxDepth)
{
List extends FileObj> files = inFile.listFiles();
if (CollectionUtil.hasValues(files))
{
for (FileObj file : files)
{
recursivelyExtract(file, inCurrentDepth + 1);
}
}
}
}
else if (null == mFilter
|| mFilter.accept(inFile))
{
String rootStrippedPath = getRootStrippedPath(inFile, mRootPath);
mFileTimestampMap.put(rootStrippedPath, inFile);
inFile.setAttribute(FileObj.LAST_MODIFIED_ATTR, inFile.lastModified());
}
}
//--------------------------------------------------------------------------
private String getRootStrippedPath(FileObj inFile, String inRootPath)
{
String rootPath = inRootPath;
int index = inFile.getPath().indexOf(rootPath);
if (-1 == index)
{
// We couldn't match the root path to the file's path!?
if (rootPath.startsWith("/shared/"))
{
rootPath = rootPath.substring(7);
index = inFile.getPath().indexOf(rootPath);
}
else
{
throw new RuntimeIOException("Couldn't find root path " + StringUtil.singleQuote(rootPath)
+ " in file path " + StringUtil.singleQuote(inFile.getPath()) + "!");
}
}
return inFile.getPath().substring(index + rootPath.length());
}
//--------------------------------------------------------------------------
private void recursivelyCompare(FileObj inFile, List inChangedFiles, String inRootPath, int inCurrentDepth)
{
if (inFile.isDirectory())
{
if (mMaxDepth <= 0 || inCurrentDepth < mMaxDepth)
{
List extends FileObj> files = inFile.listFiles();
if (CollectionUtil.hasValues(files))
{
for (FileObj file : files)
{
recursivelyCompare(file, inChangedFiles, inRootPath, inCurrentDepth + 1);
}
}
}
}
else if (null == mFilter
|| mFilter.accept(inFile))
{
String rootStrippedPath = getRootStrippedPath(inFile, inRootPath);
FileObj snapshotFile = mFileTimestampMap.get(rootStrippedPath);
if (null == snapshotFile
|| CompareUtil.compare(snapshotFile.lastModified(), inFile.lastModified()) != 0)
{
inChangedFiles.add(inFile);
}
}
}
//--------------------------------------------------------------------------
private void recursivelyExtractEvents(FileObj inFile,
List inEvents,
String inRootPath,
Map inPrevFileMap,
int inCurrentDepth)
{
String rootStrippedPath = getRootStrippedPath(inFile, inRootPath);
FileObj snapshotFile = inPrevFileMap.remove(rootStrippedPath);
if (inFile.isDirectory())
{
if (mMaxDepth <= 0 || inCurrentDepth < mMaxDepth)
{
List extends FileObj> files = inFile.listFiles();
if (CollectionUtil.hasValues(files))
{
for (FileObj file : files)
{
recursivelyExtractEvents(file, inEvents, inRootPath, inPrevFileMap, inCurrentDepth + 1);
}
}
}
}
else if (null == mFilter
|| mFilter.accept(inFile))
{
if (null == snapshotFile)
{
inEvents.add(new FileObjEvent(inFile, FileEventType.CREATED));
}
else if (! snapshotFile.lastModified().equals(inFile.lastModified()))
{
inEvents.add(new FileObjEvent(inFile, FileEventType.MODIFIED));
}
}
}
}