package com.beijunyi.parallelgit.filesystem.io; import java.io.IOException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nonnull; import javax.annotation.Nullable; import com.beijunyi.parallelgit.filesystem.GfsObjectService; import com.beijunyi.parallelgit.filesystem.exceptions.IncompatibleFileModeException; import com.beijunyi.parallelgit.utils.io.GitFileEntry; import com.beijunyi.parallelgit.utils.io.TreeSnapshot; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import static com.beijunyi.parallelgit.utils.io.GitFileEntry.*; import static java.util.Collections.*; import static org.eclipse.jgit.lib.FileMode.TREE; public class DirectoryNode extends Node<TreeSnapshot, Map<String, Node>> { protected DirectoryNode(ObjectId id, GfsObjectService objService) { super(id, TREE, objService); } protected DirectoryNode(GfsObjectService objService) { super(TREE, objService); } protected DirectoryNode(ObjectId id, DirectoryNode parent) { super(id, TREE, parent); } protected DirectoryNode(DirectoryNode parent) { super(TREE, parent); } @Nonnull public static DirectoryNode fromTree(ObjectId id, DirectoryNode parent) { return new DirectoryNode(id, parent); } @Nonnull public static DirectoryNode newDirectory(DirectoryNode parent) { return new DirectoryNode(parent); } @Override protected Class<? extends TreeSnapshot> getSnapshotType() { return TreeSnapshot.class; } @Override public long getSize() throws IOException { return 0; } @Override public void updateOrigin(GitFileEntry entry) throws IOException { super.updateOrigin(entry); if(isInitialized()) { if(origin.isSubtree()) { snapshot = objService.readTree(entry.getId()); Set<String> updatedChildren = updateChildrenOrigins(); Collection<Node> notUpdatedNodes = findNotUpdatedChildren(updatedChildren); updateOriginsToTrivial(notUpdatedNodes); } else { updateOriginsToTrivial(data.values()); } } } public void updateOrigin(ObjectId id) throws IOException { updateOrigin(newTreeEntry(id)); } @Nonnull @Override protected Map<String, Node> loadData(TreeSnapshot snapshot) throws IOException { Map<String, Node> ret = getDefaultData(); boolean updateOrigin = origin != null && origin.getId().equals(snapshot.getId()); for(Map.Entry<String, GitFileEntry> child : snapshot.getData().entrySet()) { GitFileEntry entry = child.getValue(); Node node = Node.fromEntry(entry, this); ret.put(child.getKey(), node); if(updateOrigin) node.updateOrigin(entry); } return ret; } @Override protected boolean isTrivial(Map<String, Node> data) throws IOException { boolean ret = true; for(Node child : data.values()) if(!child.isTrivial()) { ret = false; break; } return ret; } @Nonnull protected TreeSnapshot captureData(Map<String, Node> data, boolean persist) throws IOException { SortedMap<String, GitFileEntry> entries = new TreeMap<>(); for(Map.Entry<String, Node> child : data.entrySet()) { Node node = child.getValue(); ObjectId id = node.getObjectId(persist); if(!isTrivial(id)) entries.put(child.getKey(), newEntry(id, node.getMode())); } return TreeSnapshot.capture(entries); } @Nonnull @Override public Node clone(DirectoryNode parent) throws IOException { DirectoryNode ret; if(isInitialized()) { ret = DirectoryNode.newDirectory(parent); for(Map.Entry<String, Node> child : data.entrySet()) { String name = child.getKey(); Node node = child.getValue(); ret.addChild(name, node.clone(ret), false); } } else if(id != null) { ret = DirectoryNode.fromTree(id, parent); parent.getObjectService().pullObject(id, objService); } else throw new IllegalStateException(); return ret; } @Nonnull public List<String> listChildren() throws IOException { List<String> ret = new ArrayList<>(getData().keySet()); Collections.sort(ret); return unmodifiableList(ret); } public boolean hasChild(String name) throws IOException { return getData().containsKey(name); } @Nullable public Node getChild(String name) throws IOException { return getData().get(name); } public boolean addChild(String name, Node child, boolean replace) throws IOException { if(!replace && getData().containsKey(name)) return false; if(snapshot != null) { GitFileEntry origin = snapshot.getChild(name); if(!origin.isMissing()) child.updateOrigin(origin); } getData().put(name, child); id = null; invalidateParentCache(); return true; } public boolean removeChild(String name) throws IOException { Node removed = getData().remove(name); if(removed != null) { removed.exile(); id = null; invalidateParentCache(); return true; } return false; } @Override protected void checkFileMode(FileMode proposed) { if(!TREE.equals(proposed)) throw new IncompatibleFileModeException(TREE, proposed); } @Nonnull @Override protected Map<String, Node> getDefaultData() { return new ConcurrentHashMap<>(); } @Nonnull private Set<String> updateChildrenOrigins() throws IOException { Set<String> ret = new HashSet<>(); for(Map.Entry<String, GitFileEntry> child : snapshot.getData().entrySet()) { String name = child.getKey(); Node node = data.get(name); if(node != null && !node.getOrigin().equals(child.getValue())) node.updateOrigin(child.getValue()); ret.add(name); } return unmodifiableSet(ret); } @Nonnull private Collection<Node> findNotUpdatedChildren(Set<String> updatedChildren) throws IOException{ List<Node> ret = new ArrayList<>(); for(Map.Entry<String, Node> child : data.entrySet()) { String name = child.getKey(); if(!updatedChildren.contains(name)) ret.add(child.getValue()); } return unmodifiableList(ret); } private void updateOriginsToTrivial(Collection<Node> nodes) throws IOException { for(Node node : nodes) { node.updateOrigin(missingEntry()); } } @Override protected void exile() { super.exile(); if(isInitialized()) { for(Node child : data.values()) child.exile(); } } }