class DefaultBlockFileSystem implements BlockFileSystem {


    /**
     * Pointers per BAT block.
     */
    final private static int POINTERS_PER_BAT = 128;


    /**
     * Size of a BAT pointer in bytes.  (In other words, 4).
     */
    final private static int BAT_POINTER_SIZE = BLOCK_SIZE / POINTERS_PER_BAT;

    
    /**
     * The number of BAT pointers in the header block.  After this many 
     * BAT blocks, the XBAT blocks must be consulted.
     */
    final private static int HEADER_BAT_LIMIT = 109;
    
    
    /**
     * The size of an entry record in bytes.
     */
    final private static int ENTRY_SIZE = 128;
    
    
    /**
     * The number of entries that can fit in a block.
     */
    final private static int ENTRIES_PER_BLOCK = BLOCK_SIZE / ENTRY_SIZE;


    /**
     * The .doc file as a stream.
     */
    private SeekInputStream input;
    
    
    /**
     * The header block.
     */
    private HeaderBlock header;


    /**
     * Cache of BAT and XBAT blocks.
     */
    private Map<Integer,ByteBuffer> cache;
    
    
    /**
     * Constructor.
     * 
     * @param input   the file to read from
     * @param batCacheSize  number of BAT and XBAT blocks to cache
     * @throws IOException  if an IO error occurs
     */
    public DefaultBlockFileSystem(SeekInputStream input, int batCacheSize)
    throws IOException {
        this.input = input;
        byte[] temp = new byte[BLOCK_SIZE];
        ArchiveUtils.readFully(input, temp);
        this.header = new HeaderBlock(ByteBuffer.wrap(temp));
        this.cache = new LRU<Integer,ByteBuffer>(batCacheSize);
    }


    public Entry getRoot() throws IOException {
        // Position to the first block of the entry list.
        int block = header.getEntriesStart();
        input.position((block + 1) * BLOCK_SIZE);
        
        // The root entry is always entry #0.
        return new DefaultEntry(this, input, 0);
    }


    /**
     * Returns the entry with the given number.
     * 
     * @param entryNumber   the number of the entry to return
     * @return   that entry, or null if no such entry exists
     * @throws IOException  if an IO error occurs
     */
    protected Entry getEntry(int entryNumber) throws IOException {
        // Entry numbers < 0 typically indicate an end-of-stream.
        if (entryNumber < 0) {
            return null;
        }
        
        // It's impossible to check against the upper bound, because the
        // upper bound is not recorded anywhere.
        
        // Advance to the block containing the desired entry.
        int blockCount = entryNumber / ENTRIES_PER_BLOCK;
        int remainder = entryNumber % ENTRIES_PER_BLOCK;        
        int block = header.getEntriesStart();
        for (int i = 0; i < blockCount; i++) {
            block = getNextBlock(block);
        }
        
        if (block < 0) {
            // Given entry number exceeded the number of available entries.
            return null;
        }

        int filePos = (block + 1) * BLOCK_SIZE + remainder * ENTRY_SIZE;
        input.position(filePos);
        
        return new DefaultEntry(this, input, entryNumber);
    }


    public int getNextBlock(int block) throws IOException {<FILL_FUNCTION_BODY>}

    
    /**
     * Looks up the block number of a BAT block.
     * 
     * @param headerIndex  
     * @return
     * @throws IOException
     */
    private int batLookup(int headerIndex) throws IOException {
        if (headerIndex < HEADER_BAT_LIMIT + 1) {
            return header.getBATBlockNumber(headerIndex);
        }
        
        // Find the XBAT block of interest
        headerIndex -= HEADER_BAT_LIMIT;
        int xbatBlockNumber = headerIndex / POINTERS_PER_BAT;
        xbatBlockNumber += header.getExtendedBATStart();
        ByteBuffer xbat = getBATBlock(xbatBlockNumber);

        // Find the bat Block number inside the XBAT block
        int xbatBlockIndex = headerIndex % POINTERS_PER_BAT;
        return xbat.getInt(xbatBlockIndex * BAT_POINTER_SIZE);
    }

    
    /**
     * Returns the BAT block with the given block number.
     * If the BAT block were previously cached, then the cached version
     * is returned.  Otherwise, the file pointer is repositioned to 
     * the start of the given block, and the 512 bytes are read and
     * stored in the cache.
     * 
     * @param block   the block number of the BAT block to return
     * @return   the BAT block
     * @throws IOException
     */
    private ByteBuffer getBATBlock(int block) throws IOException {
        ByteBuffer r = cache.get(block);
        if (r != null) {
            return r;
        }

        byte[] buf = new byte[BLOCK_SIZE];
        input.position((block + 1) * BLOCK_SIZE);
        ArchiveUtils.readFully(input, buf);

        r = ByteBuffer.wrap(buf);
        r.order(ByteOrder.LITTLE_ENDIAN);
        cache.put(block, r);
        return r;
    }


    public SeekInputStream getRawInput() {
        return input;
    }
}

        if (block < 0) {
            return block;
        }
        
        // Index into the header array of BAT blocks.
        int headerIndex = block / POINTERS_PER_BAT;
        
        // Index within that BAT block of the block we're interested in.
        int batBlockIndex = block % POINTERS_PER_BAT;

        int batBlockNumber = batLookup(headerIndex);
        ByteBuffer batBlock = getBATBlock(batBlockNumber);
        return batBlock.getInt(batBlockIndex * BAT_POINTER_SIZE);
    
<no_super_class>