class HeapDisk {

  /** Fixed size of each block for this disk. */
  private final int blockSize;

  /** Maximum total number of blocks that the disk may contain at any time. */
  private final int maxBlockCount;

  /** Maximum total number of unused blocks that may be cached for reuse at any time. */
  private final int maxCachedBlockCount;

  /**
   * Cache of free blocks to be allocated to files. While this is stored as a file, it isn't used
   * like a normal file: only the methods for accessing its blocks are used.
   */
  @VisibleForTesting final RegularFile blockCache;

  /** The current total number of blocks that are currently allocated to files. */
  private int allocatedBlockCount;

  /** Creates a new disk using settings from the given configuration. */
  public HeapDisk(Configuration config) {
    this.blockSize = config.blockSize;
    this.maxBlockCount = toBlockCount(config.maxSize, blockSize);
    this.maxCachedBlockCount =
        config.maxCacheSize == -1 ? maxBlockCount : toBlockCount(config.maxCacheSize, blockSize);
    this.blockCache = createBlockCache(maxCachedBlockCount);
  }

  /**
   * Creates a new disk with the given {@code blockSize}, {@code maxBlockCount} and {@code
   * maxCachedBlockCount}.
   */
  public HeapDisk(int blockSize, int maxBlockCount, int maxCachedBlockCount) {
    checkArgument(blockSize > 0, "blockSize (%s) must be positive", blockSize);
    checkArgument(maxBlockCount > 0, "maxBlockCount (%s) must be positive", maxBlockCount);
    checkArgument(
        maxCachedBlockCount >= 0, "maxCachedBlockCount (%s) must be non-negative",
        maxCachedBlockCount);
    this.blockSize = blockSize;
    this.maxBlockCount = maxBlockCount;
    this.maxCachedBlockCount = maxCachedBlockCount;
    this.blockCache = createBlockCache(maxCachedBlockCount);
  }

  /** Returns the nearest multiple of {@code blockSize} that is <= {@code size}. */
  private static int toBlockCount(long size, int blockSize) {
    return (int) LongMath.divide(size, blockSize, RoundingMode.FLOOR);
  }

  private RegularFile createBlockCache(int maxCachedBlockCount) {
    // This file is just for holding blocks so things like the creation time don't matter
    return new RegularFile(
        -1,
        SystemFileTimeSource.INSTANCE.now(),
        this,
        new byte[Math.min(maxCachedBlockCount, 8192)][],
        0,
        0);
  }

  /** Returns the size of blocks created by this disk. */
  public int blockSize() {
    return blockSize;
  }

  /**
   * Returns the total size of this disk. This is the maximum size of the disk and does not reflect
   * the amount of data currently allocated or cached.
   */
  public synchronized long getTotalSpace() {
    return maxBlockCount * (long) blockSize;
  }

  /**
   * Returns the current number of unallocated bytes on this disk. This is the maximum number of
   * additional bytes that could be allocated and does not reflect the number of bytes currently
   * actually cached in the disk.
   */
  public synchronized long getUnallocatedSpace() {
    return (maxBlockCount - allocatedBlockCount) * (long) blockSize;
  }

  /** Allocates the given number of blocks and adds them to the given file. */
  public synchronized void allocate(RegularFile file, int count) throws IOException {<FILL_FUNCTION_BODY>}

  /** Frees all blocks in the given file. */
  public void free(RegularFile file) {
    free(file, file.blockCount());
  }

  /** Frees the last {@code count} blocks from the given file. */
  public synchronized void free(RegularFile file, int count) {
    int remainingCacheSpace = maxCachedBlockCount - blockCache.blockCount();
    if (remainingCacheSpace > 0) {
      file.copyBlocksTo(blockCache, Math.min(count, remainingCacheSpace));
    }
    file.truncateBlocks(file.blockCount() - count);

    allocatedBlockCount -= count;
  }
}

    int newAllocatedBlockCount = allocatedBlockCount + count;
    if (newAllocatedBlockCount > maxBlockCount) {
      throw new IOException("out of disk space");
    }

    int newBlocksNeeded = Math.max(count - blockCache.blockCount(), 0);

    for (int i = 0; i < newBlocksNeeded; i++) {
      file.addBlock(new byte[blockSize]);
    }

    if (newBlocksNeeded != count) {
      blockCache.transferBlocksTo(file, count - newBlocksNeeded);
    }

    allocatedBlockCount = newAllocatedBlockCount;
  
<no_super_class>