code: package com.beijunyi.parallelgit.filesystem;
  
  import java.io.Closeable;
  import java.io.IOException;
  import java.nio.file.ClosedFileSystemException;
  import javax.annotation.Nonnull;
  
  import com.beijunyi.parallelgit.utils.BlobUtils;
  import com.beijunyi.parallelgit.utils.io.*;
  import org.eclipse.jgit.lib.*;
  
  import static org.eclipse.jgit.lib.Constants.*;
  
  public class GfsObjectService implements Closeable {
  
    private final Repository repo;
    private final ObjectReader reader;
    private final ObjectInserter inserter;
  
    private volatile boolean closed = false;
  
    GfsObjectService(final Repository repo) {
      this.repo = repo;
      this.reader = repo.newObjectReader();
      this.inserter = repo.newObjectInserter();
    }
  
    @Nonnull
    public Repository getRepository() {
      return repo;
    }
  
    @Nonnull
    public ObjectLoader open(AnyObjectId objectId) throws IOException {
      checkClosed();
      synchronized(reader) {
        return reader.open(objectId);
      }
    }
  
    public boolean hasObject(AnyObjectId objectId) throws IOException {
      checkClosed();
      synchronized(reader) {
        return reader.has(objectId);
      }
    }
  
    @Nonnull
    public <S extends ObjectSnapshot> S read(ObjectId id, Class<S> type) throws IOException {
      if(BlobSnapshot.class.isAssignableFrom(type))
        return type.cast(readBlob(id));
      if(TreeSnapshot.class.isAssignableFrom(type))
        return type.cast(readTree(id));
      throw new UnsupportedOperationException(type.getName());
    }
  
    @Nonnull
    public BlobSnapshot readBlob(ObjectId id) throws IOException {
      checkClosed();
      synchronized(reader) {
        return BlobUtils.readBlob(id, reader);
      }
    }
  
    public long getBlobSize(ObjectId id) throws IOException {
      checkClosed();
      synchronized(reader) {
        return BlobUtils.getBlobSize(id, reader);
      }
    }
  
    @Nonnull
    public TreeSnapshot readTree(ObjectId id) throws IOException {
      checkClosed();
      synchronized(reader) {
        return TreeSnapshot.load(id, reader);
      }
    }
  
    @Nonnull
    public ObjectId write(ObjectSnapshot snapshot) throws IOException {
      return snapshot.save(inserter);
    }
  
    public void pullObject(ObjectId id, boolean flush, GfsObjectService sourceObjService) throws IOException {
      if(!hasObject(id)) {
        ObjectLoader loader = sourceObjService.open(id);
        switch(loader.getType()) {
          case OBJ_TREE:
            pullTree(id, sourceObjService);
            break;
          case OBJ_BLOB:
            pullBlob(id, sourceObjService);
            break;
          default:
            throw new UnsupportedOperationException(id.toString());
        }
        if(flush) flush();
      }
    }
  
    public void pullObject(ObjectId id, GfsObjectService sourceObjService) throws IOException {
      pullObject(id, true, sourceObjService);
    }
  
    public void flush() throws IOException {
      checkClosed();
      synchronized(inserter) {
        inserter.flush();
      }
    }
  
    @Override
    public synchronized void close() {
      if(!closed) {
        closed = true;
        reader.close();
        inserter.close();
        repo.close();
      }
    }
  
    private void pullTree(ObjectId id, GfsObjectService sourceObjService) throws IOException {
      TreeSnapshot tree = sourceObjService.readTree(id);
      for(GitFileEntry entry : tree.getData().values())
        pullObject(entry.getId(), false, sourceObjService);
      write(tree);
    }
  
    private void pullBlob(ObjectId id, GfsObjectService sourceObjService) throws IOException {
      write(sourceObjService.readBlob(id));
    }
  
    private void checkClosed() {
      if(closed) throw new ClosedFileSystemException();
    }
  
  }
  