code: package com.beijunyi.parallelgit.utils;
  
  import java.io.IOException;
  import java.util.Iterator;
  import javax.annotation.Nonnull;
  import javax.annotation.Nullable;
  
  import com.beijunyi.parallelgit.utils.exceptions.NoSuchCacheDirectoryException;
  import com.beijunyi.parallelgit.utils.exceptions.NoSuchCacheEntryException;
  import com.beijunyi.parallelgit.utils.exceptions.NoSuchCommitException;
  import com.beijunyi.parallelgit.utils.io.CacheEntryUpdate;
  import com.beijunyi.parallelgit.utils.io.CacheIterator;
  import com.beijunyi.parallelgit.utils.io.CacheNode;
  import org.eclipse.jgit.dircache.*;
  import org.eclipse.jgit.lib.*;
  
  import static com.beijunyi.parallelgit.utils.TreeUtils.normalizeNodePath;
  import static org.eclipse.jgit.dircache.DirCacheEntry.STAGE_0;
  import static org.eclipse.jgit.lib.Constants.encode;
  import static org.eclipse.jgit.lib.FileMode.*;
  
  public final class CacheUtils {
  
    @Nonnull
    public static DirCacheEntry newDirCacheEntry(String path) {
      return new DirCacheEntry(normalizeNodePath(path));
    }
  
    @Nonnull
    public static DirCacheEditor.DeletePath deleteEntry(String path) {
      return new DirCacheEditor.DeletePath(normalizeNodePath(path));
    }
  
    @Nonnull
    public static DirCacheEditor.DeleteTree deleteChildren(String path) {
      return new DirCacheEditor.DeleteTree(normalizeNodePath(path));
    }
  
    public static void loadTree(AnyObjectId treeId, DirCache cache, ObjectReader reader) throws IOException {
      addTree("", treeId, cache, reader);
    }
  
    public static void loadRevision(AnyObjectId revision, DirCache cache, ObjectReader reader) throws IOException {
      loadTree(CommitUtils.getCommit(revision, reader).getTree(), cache, reader);
    }
  
    @Nonnull
    public static DirCache forTree(AnyObjectId treeId, ObjectReader reader) throws IOException {
      DirCache cache = DirCache.newInCore();
      loadTree(treeId, cache, reader);
      return cache;
    }
  
    @Nonnull
    public static DirCache forTree(AnyObjectId treeId, Repository repo) throws IOException {
      try(ObjectReader reader = repo.newObjectReader()) {
        return forTree(treeId, reader);
      }
    }
  
    @Nonnull
    public static DirCache forRevision(AnyObjectId revision, ObjectReader reader) throws IOException {
      DirCache cache = DirCache.newInCore();
      loadRevision(revision, cache, reader);
      return cache;
    }
  
    @Nonnull
    public static DirCache forRevision(AnyObjectId revision, Repository repo) throws IOException {
      try(ObjectReader reader = repo.newObjectReader()) {
        return forRevision(revision, reader);
      }
    }
  
    @Nonnull
    public static DirCache forRevision(Ref revision, Repository repo) throws IOException {
      return forRevision(revision.getObjectId(), repo);
    }
  
    @Nonnull
    public static DirCache forRevision(String revision, Repository repo) throws IOException {
      AnyObjectId revisionId = repo.resolve(revision);
      if(revisionId == null)
        throw new NoSuchCommitException(revision);
      return forRevision(revisionId, repo);
    }
  
    @Nonnull
    public static DirCacheBuilder keepEverything(DirCache cache) {
      DirCacheBuilder builder = cache.builder();
      int count = cache.getEntryCount();
      if(count > 0)
        builder.keep(0, count);
      return builder;
    }
  
    public static void addTree(String path, AnyObjectId treeId, DirCacheBuilder builder, ObjectReader reader) throws IOException {
      builder.addTree(encode(normalizeNodePath(path)), STAGE_0, reader, treeId);
    }
  
    public static void addTree(String path, AnyObjectId treeId, DirCache cache, ObjectReader reader) throws IOException {
      DirCacheBuilder builder = keepEverything(cache);
      addTree(path, treeId, builder, reader);
      builder.finish();
    }
  
    public static void addFile(String path, FileMode mode, AnyObjectId blobId, DirCacheBuilder builder) {
      DirCacheEntry entry = newDirCacheEntry(path);
      entry.setFileMode(mode);
      entry.setObjectId(blobId);
      builder.add(entry);
    }
  
    public static void addFile(String path, FileMode mode, AnyObjectId blobId, DirCache cache) {
      DirCacheBuilder builder = keepEverything(cache);
      addFile(path, mode, blobId, builder);
      builder.finish();
    }
  
    public static void addFile(String path, AnyObjectId blobId, DirCache cache) {
      addFile(path, REGULAR_FILE, blobId, cache);
    }
  
    public static void deleteFile(String path, DirCacheEditor editor) {
      editor.add(deleteEntry(path));
    }
  
    public static void deleteFile(String path, DirCache cache) {
      DirCacheEditor editor = cache.editor();
      deleteFile(path, editor);
      editor.finish();
    }
  
    public static void deleteDirectory(String path, DirCacheEditor editor) {
      editor.add(deleteChildren(path));
    }
  
    public static void deleteDirectory(String path, DirCache cache) {
      DirCacheEditor editor = cache.editor();
      deleteDirectory(path, editor);
      editor.finish();
    }
  
    @Nullable
    public static DirCacheEntry getEntry(String path, DirCache cache) {
      return cache.getEntry(normalizeNodePath(path));
    }
  
    public static void updateFile(CacheEntryUpdate update, DirCacheEditor editor) {
      editor.add(update);
    }
  
    public static void updateFile(CacheEntryUpdate update, DirCache cache) {
      DirCacheEditor editor = cache.editor();
      updateFile(update, editor);
      editor.finish();
    }
  
    public static void updateFileBlob(String path, ObjectId blob, FileMode mode, DirCache cache) {
      updateFile(new CacheEntryUpdate(path).setNewBlob(blob).setNewFileMode(mode), cache);
    }
  
    public static void updateFileBlob(String path, ObjectId blob, DirCache cache) {
      updateFile(new CacheEntryUpdate(path).setNewBlob(blob), cache);
    }
  
    public static void updateFileMode(String path, FileMode mode, DirCache cache) {
      updateFile(new CacheEntryUpdate(path).setNewFileMode(mode), cache);
    }
  
    public static int findEntry(String path, DirCache cache) {
      return cache.findEntry(normalizeNodePath(path));
    }
  
    public static boolean entryExists(String path, DirCache cache) {
      return findEntry(path, cache) >= 0;
    }
  
    @Nonnull
    public static ObjectId getBlob(String path, DirCache cache) throws NoSuchCacheEntryException {
      return ensureEntry(path, cache).getObjectId();
    }
  
    @Nonnull
    public static FileMode getFileMode(String path, DirCache cache) throws NoSuchCacheEntryException {
      return ensureEntry(path, cache).getFileMode();
    }
  
  
    public static boolean isFile(String path, DirCache cache) {
      return entryExists(path, cache);
    }
  
    public static boolean isSymbolicLink(String path, DirCache cache) {
      DirCacheEntry entry = getEntry(path, cache);
      return entry != null && entry.getFileMode() == SYMLINK;
    }
  
    public static boolean isRegularFile(String path, DirCache cache) {
      DirCacheEntry entry = getEntry(path, cache);
      return entry != null && entry.getFileMode() == REGULAR_FILE;
    }
  
    public static boolean isExecutableFile(String path, DirCache cache) {
      DirCacheEntry entry = getEntry(path, cache);
      return entry != null && entry.getFileMode() == EXECUTABLE_FILE;
    }
  
    public static boolean isRegularOrExecutableFile(String path, DirCache cache) {
      DirCacheEntry entry = getEntry(path, cache);
      return entry != null
               && (entry.getFileMode() == REGULAR_FILE || entry.getFileMode() == EXECUTABLE_FILE);
    }
  
    public static boolean isNonEmptyDirectory(String path, DirCache cache) {
      path = normalizeNodePath(path) + "/";
      if(path.equals("/")) // if it is root
        return true;
  
      int startIndex = cache.findEntry(path); // find the index of this path
      if(startIndex < 0) // when the path is a directory, its index is negative
        startIndex = -(startIndex + 1); // indices of children paths inside this directory start from -(index + 1)
  
      if(startIndex >= cache.getEntryCount()) // if beyond the total
        return false;
  
      String childPath = cache.getEntry(startIndex).getPathString(); // potentially the first child inside this directory
      return childPath.startsWith(path); // if it is indeed a child path, then this directory is non-trivial
    }
  
    @Nonnull
    public static Iterator<CacheNode> iterateDirectory(String path, boolean recursive, DirCache cache) {
      path = normalizeNodePath(path);
      DirCacheEntry[] entries = cache.getEntriesWithin(path);
      if(entries.length == 0)
        throw new NoSuchCacheDirectoryException("/" + path);
      return recursive ? new CacheIterator(entries) : new CacheIterator(entries, path);
    }
  
    @Nonnull
    public static Iterator<CacheNode> iterateDirectory(String path, DirCache cache) {
      return iterateDirectory(path, false, cache);
    }
  
    @Nonnull
    public static ObjectId writeTree(DirCache cache, Repository repo) throws IOException {
      try(ObjectInserter inserter = repo.newObjectInserter()) {
        ObjectId tree = cache.writeTree(inserter);
        inserter.flush();
        return tree;
      }
    }
  
    @Nonnull
    private static DirCacheEntry ensureEntry(String path, DirCache cache) {
      DirCacheEntry entry = getEntry(path, cache);
      if(entry == null)
        throw new NoSuchCacheEntryException("/" + normalizeNodePath(path));
      return entry;
    }
  
  }
  