408 lines
14 KiB
Java
408 lines
14 KiB
Java
/* ====================================================================
|
|
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 and XBAT) sectors specified.
|
|
* 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 numBAT, final int numXBAT) {
|
|
int size = 1; // Header isn't FAT addressed
|
|
size += (numBAT * bigBlockSize.getBATEntriesPerBlock());
|
|
size += (numXBAT * bigBlockSize.getXBATEntriesPerBlock());
|
|
return size * bigBlockSize.getBigBlockSize();
|
|
}
|
|
public static int calculateMaximumSize(final HeaderBlock header)
|
|
{
|
|
return calculateMaximumSize(header.getBigBlockSize(), header.getBATCount(), header.getXBATCount());
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
|
|
// Are we in the BAT or XBAT range
|
|
int batRangeEndsAt = bigBlockSize.getBATEntriesPerBlock() *
|
|
header.getBATCount();
|
|
|
|
if(offset < batRangeEndsAt) {
|
|
int whichBAT = (int)Math.floor(offset / bigBlockSize.getBATEntriesPerBlock());
|
|
int index = offset % bigBlockSize.getBATEntriesPerBlock();
|
|
return new BATBlockAndIndex( index, bats.get(whichBAT) );
|
|
}
|
|
|
|
// XBATs hold slightly less
|
|
int relOffset = offset - batRangeEndsAt;
|
|
int whichXBAT = (int)Math.floor(relOffset / bigBlockSize.getXBATEntriesPerBlock());
|
|
int index = relOffset % bigBlockSize.getXBATEntriesPerBlock();
|
|
return new BATBlockAndIndex(
|
|
index,
|
|
bats.get(header.getBATCount() + whichXBAT)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
}
|
|
}
|
|
|