convex.dlfs.DLFSNode Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of convex-core Show documentation
Show all versions of convex-core Show documentation
Convex core libraries and common utilities
The newest version!
package convex.dlfs;
import convex.core.data.ABlob;
import convex.core.data.ACell;
import convex.core.data.AHashMap;
import convex.core.data.AString;
import convex.core.data.AVector;
import convex.core.data.Blob;
import convex.core.data.MapEntry;
import convex.core.data.Maps;
import convex.core.data.Vectors;
import convex.core.data.prim.CVMLong;
import convex.core.util.MergeFunction;
import convex.core.util.Utils;
/**
* Static utility class for working with DLFS Node structures
*/
public class DLFSNode {
static final AHashMap> EMPTY_CONTENTS = Maps.empty();
static final AHashMap> NIL_CONTENTS = null;
static final Blob NIL_DATA = null;
static final Blob EMPTY_DATA = Blob.EMPTY;
static final ACell EMPTY_METADATA = null;
static final CVMLong EMPTY_TIME = CVMLong.ZERO;
private static final AVector EMPTY_DIRECTORY=Vectors.of(EMPTY_CONTENTS,NIL_DATA,EMPTY_METADATA,EMPTY_TIME);
private static final AVector EMPTY_FILE=Vectors.of(NIL_CONTENTS,EMPTY_DATA,EMPTY_METADATA,EMPTY_TIME);
private static final AVector TOMBSTONE=Vectors.of(NIL_CONTENTS,NIL_DATA,EMPTY_METADATA,EMPTY_TIME);
// node structure contents
public static final long NODE_LENGTH = 4;
public static final int POS_DIR = 0;
public static final int POS_DATA = 1;
public static final int POS_METADATA = 2;
public static final int POS_UTIME = 3;
public static boolean isDirectory(AVector node) {
if (node==null) return false;
return node.get(POS_DIR)!=null;
}
public static boolean isRegularFile(AVector node) {
if (node==null) return false;
return node.get(POS_DATA) instanceof ABlob;
}
/**
* Navigate down a path relative to a DLFS Node.
* @param node Node from which to navigate
* @param path Path to navigate from (assumed to be relative)
* @return Found node, or null if doesn't exist
*/
@SuppressWarnings("unchecked")
public static AVector navigate(AVector node, DLPath path) {
if (path==null) return null;
int n=path.getNameCount();
for (int i=0; i> dir=(AHashMap>) node.get(POS_DIR);
if (dir==null) return null;
AVector child=dir.get(compName);
if (child==null) return null;
node=child;
}
return node;
}
/**
* Gets the directory entries for a node
* @param dirNode Node which is assumed to be a directory
* @return Map of directory entries, or null if not a directory
*/
@SuppressWarnings("unchecked")
public static AHashMap> getDirectoryEntries(AVector dirNode) {
if ((dirNode==null)||(dirNode.count()>) dirNode.get(POS_DIR);
}
/**
* Update a node at a path relative to a root node
* @param rootNode Root node of file system
* @param path Path relative to root
* @param newNode New node, or null to delete a node
* @param utime Timestamp to set on any directories changed
* @return Updated root node, or null if update failed (parent(s) not a directory)
*/
public static AVector updateNode(AVector rootNode, DLPath path,AVector newNode, CVMLong utime) {
int n=path.getNameCount();
if (n==0) return newNode;
if (!isDirectory(rootNode)) return null;
AString name=path.getCVMName(0);
AHashMap> entries = getDirectoryEntries(rootNode);
AVector childNode=entries.get(name);
childNode=updateNode(childNode,path.subpath(1),newNode,utime);
if (childNode==null) {
if (n==1) {
// deleting an entry at this position
entries=entries.dissoc(name);
} else {
// we failed
return null;
}
} else {
// we have an updated child
entries=entries.assoc(name, childNode);
}
AVector result=rootNode;
result=result.assoc(POS_DIR, entries);
result=result.assoc(POS_UTIME, utime);
return result;
}
/**
* Gets the data from a DLFS file node, or nil if not a regular File
*/
public static ABlob getData(AVector node) {
return (ABlob) node.get(POS_DATA);
}
/**
* Gets the metadata from a DLFS node
*/
public static Blob getMetaData(AVector node) {
return (Blob) node.get(POS_METADATA);
}
/**
* Gets the metadata from a DLFS node
*/
public static CVMLong getUTime(AVector node) {
return (CVMLong) node.get(POS_UTIME);
}
/**
*
* @param node Node to check for directory
* @param name Directory entry name
* @return Directory entry, return null if not found or node is not a directory
*/
public static MapEntry> getDirectoryEntry(AVector node, AString name) {
AHashMap> entries = getDirectoryEntries(node);
if (entries==null) return null;
MapEntry> entry = entries.getEntry(name);
return entry;
}
/**
* Returns true iff the node is a DLFS tombstone
* @param node Node to test
* @return True if a tombstone, false if anything else (including null)
*/
public static boolean isTombstone(AVector node) {
if (node==null) return false;
return (!isDirectory(node)&&!isRegularFile(node));
}
private static AVector lastTombstone=TOMBSTONE;
public static AVector createTombstone(CVMLong timestamp) {
AVector last=lastTombstone;
last= TOMBSTONE.assoc(POS_UTIME,timestamp);
lastTombstone=last;
return last;
}
private static AVector lastDirectory=EMPTY_DIRECTORY;
public static AVector createDirectory(CVMLong timestamp) {
AVector last=lastDirectory;
last= EMPTY_DIRECTORY.assoc(POS_UTIME,timestamp);
lastDirectory=last;
return last;
}
private static AVector lastEmptyFile=EMPTY_FILE;
public static AVector createEmptyFile(CVMLong timestamp) {
AVector last=lastEmptyFile;
last= EMPTY_FILE.assoc(POS_UTIME,timestamp);
lastEmptyFile=last;
return last;
}
/**
* Merges two DLFS nodes recursively. Favours newer (utime) entries in case of conflicts.
* @param a First node (non-null). Favoured in result if all else equal.
* @param b Second node (non-null)
* @param time Update time for merged changes
* @return Merged node
*/
public static AVector merge(AVector a, AVector b, CVMLong time) {
if (a.equals(b)) return a;
CVMLong timeA=getUTime(a);
CVMLong timeB=getUTime(b);
AHashMap> contA = getDirectoryEntries(a);
AHashMap> contB = getDirectoryEntries(b);
// might be equal in all content except timestamp, if so take the most recent value.
if (Utils.equals(contA, contB)) {
if (Utils.equals(getData(a), getData(b))) {
return timeA.compareTo(timeB)>=0?a:b;
}
}
if ((contA!=null)&&(contB!=null)) {
// we have two directories, so need to merge by entry name
AHashMap> mergedEntries=contA.mergeDifferences(contB, new MergeFunction>() {
@Override
public AVector merge(AVector ca, AVector cb) {
// We know values are different at this point
// nulls mean other map has a missing value
if (cb==null) return ca;
if (ca==null) return cb;
return DLFSNode.merge(ca,cb,time);
}
});
// Helps performance a lot if we can return a directly with no changes
if ((contA==mergedEntries)&&(timeA.longValue()>=time.longValue())) return a;
AVector result=createDirectory(time);
result=result.assoc(POS_DIR, mergedEntries);
return result;
} else {
// at least one in not a directory, so select based on more recent timestamp, or choose a if equal
AVector result= timeA.longValue()>=timeB.longValue()?a:b;
return result;
}
}
}