package com.beijunyi.parallelgit.filesystem.io;
import java.io.IOException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.beijunyi.parallelgit.filesystem.GfsObjectService;
import com.beijunyi.parallelgit.utils.io.GitFileEntry;
import com.beijunyi.parallelgit.utils.io.ObjectSnapshot;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import static com.beijunyi.parallelgit.filesystem.io.DirectoryNode.fromTree;
import static com.beijunyi.parallelgit.filesystem.io.FileNode.fromBlob;
import static com.beijunyi.parallelgit.utils.io.GitFileEntry.missingEntry;
import static org.eclipse.jgit.lib.FileMode.*;
import static org.eclipse.jgit.lib.ObjectId.zeroId;
public abstract class Node<Snapshot extends ObjectSnapshot, Data> {
protected final GfsObjectService objService;
protected volatile GitFileEntry origin = missingEntry();
protected volatile DirectoryNode parent;
protected volatile Snapshot snapshot;
protected volatile ObjectId id;
protected volatile FileMode mode;
protected volatile Data data;
protected Node(FileMode mode, GfsObjectService objService) {
this.objService = objService;
this.mode = mode;
initialize();
}
protected Node(ObjectId id, FileMode mode, GfsObjectService objService) {
this.objService = objService;
this.id = id;
this.mode = mode;
}
protected Node(FileMode mode, DirectoryNode parent) {
this(mode, parent.getObjectService());
this.parent = parent;
}
protected Node(ObjectId id, FileMode mode, DirectoryNode parent) {
this(id, mode, parent.getObjectService());
this.parent = parent;
}
protected Node(Data data, FileMode mode, DirectoryNode parent) {
this(mode, parent);
this.data = data;
}
@Nonnull
public static Node fromEntry(GitFileEntry entry, DirectoryNode parent) {
return TREE.equals(entry.getMode())
? fromTree(entry.getId(), parent)
: fromBlob(entry.getId(), entry.getMode(), parent);
}
@Nonnull
protected GfsObjectService getObjectService() {
return objService;
}
public boolean isExecutableFile() {
return EXECUTABLE_FILE.equals(getMode());
}
public boolean isRegularFile() {
return REGULAR_FILE.equals(getMode()) || isExecutableFile();
}
public boolean isSymbolicLink() {
return SYMLINK.equals(getMode());
}
public boolean isDirectory() {
return TREE.equals(getMode());
}
@Nonnull
public ObjectId getObjectId(boolean persist) throws IOException {
if(id == null || persist && !objService.hasObject(id)) {
Snapshot snapshot = takeSnapshot(persist);
id = snapshot != null ? snapshot.getId() : zeroId();
}
return id;
}
@Nonnull
public GitFileEntry getOrigin() {
return origin;
}
public void updateOrigin(GitFileEntry entry) throws IOException {
origin = entry;
}
@Nonnull
public FileMode getMode() {
return mode;
}
public void setMode(FileMode mode) {
checkFileMode(mode);
this.mode = mode;
invalidateParentCache();
}
public boolean isNew() throws IOException {
return origin.isMissing();
}
public boolean isModified() throws IOException {
ObjectId id = getObjectId(false);
return !origin.getId().equals(id) || !origin.getMode().equals(mode);
}
@Nonnull
protected Data getData() throws IOException {
if(data != null)
return data;
if(id == null) throw new IllegalStateException();
data = loadData(loadSnapshot(id));
return data;
}
protected boolean isTrivial() throws IOException {
return isTrivial(getObjectId(false));
}
@Nonnull
protected Snapshot loadSnapshot(ObjectId id) throws IOException {
Snapshot ret = objService.read(id, getSnapshotType());
if(origin.getId().equals(id))
snapshot = ret;
return ret;
}
@Nullable
protected Snapshot takeSnapshot(boolean persist) throws IOException {
if(data == null) throw new IllegalStateException();
if(isTrivial(data)) return null;
Snapshot snapshot = captureData(data, persist);
if(persist) objService.write(snapshot);
return snapshot;
}
protected boolean isInitialized() {
return data != null;
}
protected void initialize() {
data = getDefaultData();
}
public void reset() {
if(origin.isMissing()) throw new IllegalStateException();
reset(origin);
}
protected void reset(GitFileEntry entry) {
checkFileMode(mode);
this.id = entry.getId();
this.mode = entry.getMode();
this.data = null;
invalidateParentCache();
}
protected void invalidateParentCache() {
if(parent != null) {
parent.id = null;
parent.invalidateParentCache();
}
}
protected void exile() {
parent = null;
}
protected abstract Class<? extends Snapshot> getSnapshotType();
public abstract long getSize() throws IOException;
protected abstract void checkFileMode(FileMode proposed);
@Nonnull
protected abstract Data getDefaultData();
@Nonnull
protected abstract Data loadData(Snapshot snapshot) throws IOException;
protected abstract boolean isTrivial(Data data) throws IOException;
@Nonnull
protected abstract Snapshot captureData(Data data, boolean persist) throws IOException;
@Nonnull
protected abstract Node clone(DirectoryNode parent) throws IOException;
protected static boolean isTrivial(ObjectId id) {
return zeroId().equals(id);
}
}