| /* ==================================================================== |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| ==================================================================== */ |
| |
| package org.apache.poi.poifs.storage; |
| |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.nio.ByteBuffer; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| import org.apache.poi.poifs.common.POIFSBigBlockSize; |
| import org.apache.poi.poifs.common.POIFSConstants; |
| import org.apache.poi.util.LittleEndian; |
| |
| /** |
| * A block of block allocation table entries. BATBlocks are created |
| * only through a static factory method: createBATBlocks. |
| * |
| * @author Marc Johnson (mjohnson at apache dot org) |
| */ |
| public final class BATBlock extends BigBlock { |
| /** |
| * For a regular fat block, these are 128 / 1024 |
| * next sector values. |
| * For a XFat (DIFat) block, these are 127 / 1023 |
| * next sector values, then a chaining value. |
| */ |
| private int[] _values; |
| |
| /** |
| * Does this BATBlock have any free sectors in it? |
| */ |
| private boolean _has_free_sectors; |
| |
| /** |
| * Where in the file are we? |
| */ |
| private int ourBlockIndex; |
| |
| /** |
| * Create a single instance initialized with default values |
| */ |
| private BATBlock(POIFSBigBlockSize bigBlockSize) |
| { |
| super(bigBlockSize); |
| |
| int _entries_per_block = bigBlockSize.getBATEntriesPerBlock(); |
| _values = new int[_entries_per_block]; |
| _has_free_sectors = true; |
| |
| Arrays.fill(_values, POIFSConstants.UNUSED_BLOCK); |
| } |
| |
| /** |
| * Create a single instance initialized (perhaps partially) with entries |
| * |
| * @param entries the array of block allocation table entries |
| * @param start_index the index of the first entry to be written |
| * to the block |
| * @param end_index the index, plus one, of the last entry to be |
| * written to the block (writing is for all index |
| * k, start_index <= k < end_index) |
| */ |
| |
| private BATBlock(POIFSBigBlockSize bigBlockSize, final int [] entries, |
| final int start_index, final int end_index) |
| { |
| this(bigBlockSize); |
| for (int k = start_index; k < end_index; k++) { |
| _values[k - start_index] = entries[k]; |
| } |
| |
| // Do we have any free sectors? |
| if(end_index - start_index == _values.length) { |
| recomputeFree(); |
| } |
| } |
| |
| private void recomputeFree() { |
| boolean hasFree = false; |
| for(int k=0; k<_values.length; k++) { |
| if(_values[k] == POIFSConstants.UNUSED_BLOCK) { |
| hasFree = true; |
| break; |
| } |
| } |
| _has_free_sectors = hasFree; |
| } |
| |
| /** |
| * Create a single BATBlock from the byte buffer, which must hold at least |
| * one big block of data to be read. |
| */ |
| public static BATBlock createBATBlock(final POIFSBigBlockSize bigBlockSize, ByteBuffer data) |
| { |
| // Create an empty block |
| BATBlock block = new BATBlock(bigBlockSize); |
| |
| // Fill it |
| byte[] buffer = new byte[LittleEndian.INT_SIZE]; |
| for(int i=0; i<block._values.length; i++) { |
| data.get(buffer); |
| block._values[i] = LittleEndian.getInt(buffer); |
| } |
| block.recomputeFree(); |
| |
| // All done |
| return block; |
| } |
| |
| /** |
| * Creates a single BATBlock, with all the values set to empty. |
| */ |
| public static BATBlock createEmptyBATBlock(final POIFSBigBlockSize bigBlockSize, boolean isXBAT) { |
| BATBlock block = new BATBlock(bigBlockSize); |
| if(isXBAT) { |
| block.setXBATChain(bigBlockSize, POIFSConstants.END_OF_CHAIN); |
| } |
| return block; |
| } |
| |
| /** |
| * Create an array of BATBlocks from an array of int block |
| * allocation table entries |
| * |
| * @param entries the array of int entries |
| * |
| * @return the newly created array of BATBlocks |
| */ |
| public static BATBlock [] createBATBlocks(final POIFSBigBlockSize bigBlockSize, final int [] entries) |
| { |
| int block_count = calculateStorageRequirements(bigBlockSize, entries.length); |
| BATBlock[] blocks = new BATBlock[ block_count ]; |
| int index = 0; |
| int remaining = entries.length; |
| |
| int _entries_per_block = bigBlockSize.getBATEntriesPerBlock(); |
| for (int j = 0; j < entries.length; j += _entries_per_block) |
| { |
| blocks[ index++ ] = new BATBlock(bigBlockSize, entries, j, |
| (remaining > _entries_per_block) |
| ? j + _entries_per_block |
| : entries.length); |
| remaining -= _entries_per_block; |
| } |
| return blocks; |
| } |
| |
| /** |
| * Create an array of XBATBlocks from an array of int block |
| * allocation table entries |
| * |
| * @param entries the array of int entries |
| * @param startBlock the start block of the array of XBAT blocks |
| * |
| * @return the newly created array of BATBlocks |
| */ |
| |
| public static BATBlock [] createXBATBlocks(final POIFSBigBlockSize bigBlockSize, |
| final int [] entries, |
| final int startBlock) |
| { |
| int block_count = |
| calculateXBATStorageRequirements(bigBlockSize, entries.length); |
| BATBlock[] blocks = new BATBlock[ block_count ]; |
| int index = 0; |
| int remaining = entries.length; |
| |
| int _entries_per_xbat_block = bigBlockSize.getXBATEntriesPerBlock(); |
| if (block_count != 0) |
| { |
| for (int j = 0; j < entries.length; j += _entries_per_xbat_block) |
| { |
| blocks[ index++ ] = |
| new BATBlock(bigBlockSize, entries, j, |
| (remaining > _entries_per_xbat_block) |
| ? j + _entries_per_xbat_block |
| : entries.length); |
| remaining -= _entries_per_xbat_block; |
| } |
| for (index = 0; index < blocks.length - 1; index++) |
| { |
| blocks[ index ].setXBATChain(bigBlockSize, startBlock + index + 1); |
| } |
| blocks[ index ].setXBATChain(bigBlockSize, POIFSConstants.END_OF_CHAIN); |
| } |
| return blocks; |
| } |
| |
| /** |
| * Calculate how many BATBlocks are needed to hold a specified |
| * number of BAT entries. |
| * |
| * @param entryCount the number of entries |
| * |
| * @return the number of BATBlocks needed |
| */ |
| public static int calculateStorageRequirements(final POIFSBigBlockSize bigBlockSize, final int entryCount) |
| { |
| int _entries_per_block = bigBlockSize.getBATEntriesPerBlock(); |
| return (entryCount + _entries_per_block - 1) / _entries_per_block; |
| } |
| |
| /** |
| * Calculate how many XBATBlocks are needed to hold a specified |
| * number of BAT entries. |
| * |
| * @param entryCount the number of entries |
| * |
| * @return the number of XBATBlocks needed |
| */ |
| public static int calculateXBATStorageRequirements(final POIFSBigBlockSize bigBlockSize, final int entryCount) |
| { |
| int _entries_per_xbat_block = bigBlockSize.getXBATEntriesPerBlock(); |
| return (entryCount + _entries_per_xbat_block - 1) |
| / _entries_per_xbat_block; |
| } |
| |
| /** |
| * Calculates the maximum size of a file which is addressable given the |
| * number of FAT (BAT) sectors specified. (We don't care if those BAT |
| * blocks come from the 109 in the header, or from header + XBATS, it |
| * won't affect the calculation) |
| * |
| * The actual file size will be between [size of fatCount-1 blocks] and |
| * [size of fatCount blocks]. |
| * For 512 byte block sizes, this means we may over-estimate by up to 65kb. |
| * For 4096 byte block sizes, this means we may over-estimate by up to 4mb |
| */ |
| public static int calculateMaximumSize(final POIFSBigBlockSize bigBlockSize, |
| final int numBATs) { |
| int size = 1; // Header isn't FAT addressed |
| |
| // The header has up to 109 BATs, and extra ones are referenced |
| // from XBATs |
| // However, all BATs can contain 128/1024 blocks |
| size += (numBATs * bigBlockSize.getBATEntriesPerBlock()); |
| |
| // So far we've been in sector counts, turn into bytes |
| return size * bigBlockSize.getBigBlockSize(); |
| } |
| public static int calculateMaximumSize(final HeaderBlock header) |
| { |
| return calculateMaximumSize(header.getBigBlockSize(), header.getBATCount()); |
| } |
| |
| /** |
| * Returns the BATBlock that handles the specified offset, |
| * and the relative index within it. |
| * The List of BATBlocks must be in sequential order |
| */ |
| public static BATBlockAndIndex getBATBlockAndIndex(final int offset, |
| final HeaderBlock header, final List<BATBlock> bats) { |
| POIFSBigBlockSize bigBlockSize = header.getBigBlockSize(); |
| |
| int whichBAT = (int)Math.floor(offset / bigBlockSize.getBATEntriesPerBlock()); |
| int index = offset % bigBlockSize.getBATEntriesPerBlock(); |
| return new BATBlockAndIndex( index, bats.get(whichBAT) ); |
| } |
| |
| /** |
| * Returns the BATBlock that handles the specified offset, |
| * and the relative index within it, for the mini stream. |
| * The List of BATBlocks must be in sequential order |
| */ |
| public static BATBlockAndIndex getSBATBlockAndIndex(final int offset, |
| final HeaderBlock header, final List<BATBlock> sbats) { |
| POIFSBigBlockSize bigBlockSize = header.getBigBlockSize(); |
| |
| // SBATs are so much easier, as they're chained streams |
| int whichSBAT = (int)Math.floor(offset / bigBlockSize.getBATEntriesPerBlock()); |
| int index = offset % bigBlockSize.getBATEntriesPerBlock(); |
| return new BATBlockAndIndex( index, sbats.get(whichSBAT) ); |
| } |
| |
| private void setXBATChain(final POIFSBigBlockSize bigBlockSize, int chainIndex) |
| { |
| int _entries_per_xbat_block = bigBlockSize.getXBATEntriesPerBlock(); |
| _values[ _entries_per_xbat_block ] = chainIndex; |
| } |
| |
| /** |
| * Does this BATBlock have any free sectors in it, or |
| * is it full? |
| */ |
| public boolean hasFreeSectors() { |
| return _has_free_sectors; |
| } |
| |
| public int getValueAt(int relativeOffset) { |
| if(relativeOffset >= _values.length) { |
| throw new ArrayIndexOutOfBoundsException( |
| "Unable to fetch offset " + relativeOffset + " as the " + |
| "BAT only contains " + _values.length + " entries" |
| ); |
| } |
| return _values[relativeOffset]; |
| } |
| public void setValueAt(int relativeOffset, int value) { |
| int oldValue = _values[relativeOffset]; |
| _values[relativeOffset] = value; |
| |
| // Do we need to re-compute the free? |
| if(value == POIFSConstants.UNUSED_BLOCK) { |
| _has_free_sectors = true; |
| return; |
| } |
| if(oldValue == POIFSConstants.UNUSED_BLOCK) { |
| recomputeFree(); |
| } |
| } |
| |
| /** |
| * Record where in the file we live |
| */ |
| public void setOurBlockIndex(int index) { |
| this.ourBlockIndex = index; |
| } |
| /** |
| * Retrieve where in the file we live |
| */ |
| public int getOurBlockIndex() { |
| return ourBlockIndex; |
| } |
| |
| |
| /* ********** START extension of BigBlock ********** */ |
| |
| /** |
| * Write the block's data to an OutputStream |
| * |
| * @param stream the OutputStream to which the stored data should |
| * be written |
| * |
| * @exception IOException on problems writing to the specified |
| * stream |
| */ |
| void writeData(final OutputStream stream) |
| throws IOException |
| { |
| // Save it out |
| stream.write( serialize() ); |
| } |
| |
| void writeData(final ByteBuffer block) |
| throws IOException |
| { |
| // Save it out |
| block.put( serialize() ); |
| } |
| |
| private byte[] serialize() { |
| // Create the empty array |
| byte[] data = new byte[ bigBlockSize.getBigBlockSize() ]; |
| |
| // Fill in the values |
| int offset = 0; |
| for(int i=0; i<_values.length; i++) { |
| LittleEndian.putInt(data, offset, _values[i]); |
| offset += LittleEndian.INT_SIZE; |
| } |
| |
| // Done |
| return data; |
| } |
| |
| /* ********** END extension of BigBlock ********** */ |
| |
| |
| public static class BATBlockAndIndex { |
| private final int index; |
| private final BATBlock block; |
| private BATBlockAndIndex(int index, BATBlock block) { |
| this.index = index; |
| this.block = block; |
| } |
| public int getIndex() { |
| return index; |
| } |
| public BATBlock getBlock() { |
| return block; |
| } |
| } |
| } |
| |