1
2 /* ====================================================================
3 Licensed to the Apache Software Foundation (ASF) under one or more
4 contributor license agreements. See the NOTICE file distributed with
5 this work for additional information regarding copyright ownership.
6 The ASF licenses this file to You under the Apache License, Version 2.0
7 (the "License"); you may not use this file except in compliance with
8 the License. You may obtain a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License for the specific language governing permissions and
16 limitations under the License.
17 ==================================================================== */
18
19
20 package org.apache.poi.poifs.storage;
21
22 import java.io.IOException;
23
24 import java.util;
25
26 import org.apache.poi.poifs.common.POIFSConstants;
27 import org.apache.poi.util.IntList;
28 import org.apache.poi.util.LittleEndian;
29 import org.apache.poi.util.LittleEndianConsts;
30
31 /**
32 * This class manages and creates the Block Allocation Table, which is
33 * basically a set of linked lists of block indices.
34 * <P>
35 * Each block of the filesystem has an index. The first block, the
36 * header, is skipped; the first block after the header is index 0,
37 * the next is index 1, and so on.
38 * <P>
39 * A block's index is also its index into the Block Allocation
40 * Table. The entry that it finds in the Block Allocation Table is the
41 * index of the next block in the linked list of blocks making up a
42 * file, or it is set to -2: end of list.
43 *
44 * @author Marc Johnson (mjohnson at apache dot org)
45 */
46
47 public class BlockAllocationTableReader
48 {
49 private IntList _entries;
50
51 /**
52 * create a BlockAllocationTableReader for an existing filesystem. Side
53 * effect: when this method finishes, the BAT blocks will have
54 * been removed from the raw block list, and any blocks labeled as
55 * 'unused' in the block allocation table will also have been
56 * removed from the raw block list.
57 *
58 * @param block_count the number of BAT blocks making up the block
59 * allocation table
60 * @param block_array the array of BAT block indices from the
61 * filesystem's header
62 * @param xbat_count the number of XBAT blocks
63 * @param xbat_index the index of the first XBAT block
64 * @param raw_block_list the list of RawDataBlocks
65 *
66 * @exception IOException if, in trying to create the table, we
67 * encounter logic errors
68 */
69
70 public BlockAllocationTableReader(final int block_count,
71 final int [] block_array,
72 final int xbat_count,
73 final int xbat_index,
74 final BlockList raw_block_list)
75 throws IOException
76 {
77 this();
78 if (block_count <= 0)
79 {
80 throw new IOException(
81 "Illegal block count; minimum count is 1, got " + block_count
82 + " instead");
83 }
84
85 // acquire raw data blocks containing the BAT block data
86 RawDataBlock blocks[] = new RawDataBlock[ block_count ];
87 int limit = Math.min(block_count, block_array.length);
88 int block_index;
89
90 for (block_index = 0; block_index < limit; block_index++)
91 {
92 blocks[ block_index ] =
93 ( RawDataBlock ) raw_block_list
94 .remove(block_array[ block_index ]);
95 }
96 if (block_index < block_count)
97 {
98
99 // must have extended blocks
100 if (xbat_index < 0)
101 {
102 throw new IOException(
103 "BAT count exceeds limit, yet XBAT index indicates no valid entries");
104 }
105 int chain_index = xbat_index;
106 int max_entries_per_block = BATBlock.entriesPerXBATBlock();
107 int chain_index_offset = BATBlock.getXBATChainOffset();
108
109 for (int j = 0; j < xbat_count; j++)
110 {
111 limit = Math.min(block_count - block_index,
112 max_entries_per_block);
113 byte[] data = raw_block_list.remove(chain_index).getData();
114 int offset = 0;
115
116 for (int k = 0; k < limit; k++)
117 {
118 blocks[ block_index++ ] =
119 ( RawDataBlock ) raw_block_list
120 .remove(LittleEndian.getInt(data, offset));
121 offset += LittleEndianConsts.INT_SIZE;
122 }
123 chain_index = LittleEndian.getInt(data, chain_index_offset);
124 if (chain_index == POIFSConstants.END_OF_CHAIN)
125 {
126 break;
127 }
128 }
129 }
130 if (block_index != block_count)
131 {
132 throw new IOException("Could not find all blocks");
133 }
134
135 // now that we have all of the raw data blocks, go through and
136 // create the indices
137 setEntries(blocks, raw_block_list);
138 }
139
140 /**
141 * create a BlockAllocationTableReader from an array of raw data blocks
142 *
143 * @param blocks the raw data
144 * @param raw_block_list the list holding the managed blocks
145 *
146 * @exception IOException
147 */
148
149 BlockAllocationTableReader(final ListManagedBlock [] blocks,
150 final BlockList raw_block_list)
151 throws IOException
152 {
153 this();
154 setEntries(blocks, raw_block_list);
155 }
156
157 /**
158 * Constructor BlockAllocationTableReader
159 *
160 *
161 */
162
163 BlockAllocationTableReader()
164 {
165 _entries = new IntList();
166 }
167
168 /**
169 * walk the entries from a specified point and return the
170 * associated blocks. The associated blocks are removed from the
171 * block list
172 *
173 * @param startBlock the first block in the chain
174 * @param blockList the raw data block list
175 *
176 * @return array of ListManagedBlocks, in their correct order
177 *
178 * @exception IOException if there is a problem acquiring the blocks
179 */
180
181 ListManagedBlock [] fetchBlocks(final int startBlock,
182 final BlockList blockList)
183 throws IOException
184 {
185 List blocks = new ArrayList();
186 int currentBlock = startBlock;
187
188 while (currentBlock != POIFSConstants.END_OF_CHAIN)
189 {
190 blocks.add(blockList.remove(currentBlock));
191 currentBlock = _entries.get(currentBlock);
192 }
193 return ( ListManagedBlock [] ) blocks
194 .toArray(new ListManagedBlock[ 0 ]);
195 }
196
197 // methods for debugging reader
198
199 /**
200 * determine whether the block specified by index is used or not
201 *
202 * @param index index of block in question
203 *
204 * @return true if the specific block is used, else false
205 */
206
207 boolean isUsed(final int index)
208 {
209 boolean rval = false;
210
211 try
212 {
213 rval = _entries.get(index) != -1;
214 }
215 catch (IndexOutOfBoundsException ignored)
216 {
217 }
218 return rval;
219 }
220
221 /**
222 * return the next block index
223 *
224 * @param index of the current block
225 *
226 * @return index of the next block (may be
227 * POIFSConstants.END_OF_CHAIN, indicating end of chain
228 * (duh))
229 *
230 * @exception IOException if the current block is unused
231 */
232
233 int getNextBlockIndex(final int index)
234 throws IOException
235 {
236 if (isUsed(index))
237 {
238 return _entries.get(index);
239 }
240 else
241 {
242 throw new IOException("index " + index + " is unused");
243 }
244 }
245
246 /**
247 * Convert an array of blocks into a set of integer indices
248 *
249 * @param blocks the array of blocks containing the indices
250 * @param raw_blocks the list of blocks being managed. Unused
251 * blocks will be eliminated from the list
252 *
253 * @exception IOException
254 */
255
256 private void setEntries(final ListManagedBlock [] blocks,
257 final BlockList raw_blocks)
258 throws IOException
259 {
260 int limit = BATBlock.entriesPerBlock();
261
262 for (int block_index = 0; block_index < blocks.length; block_index++)
263 {
264 byte[] data = blocks[ block_index ].getData();
265 int offset = 0;
266
267 for (int k = 0; k < limit; k++)
268 {
269 int entry = LittleEndian.getInt(data, offset);
270
271 if (entry == POIFSConstants.UNUSED_BLOCK)
272 {
273 raw_blocks.zap(_entries.size());
274 }
275 _entries.add(entry);
276 offset += LittleEndianConsts.INT_SIZE;
277 }
278
279 // discard block
280 blocks[ block_index ] = null;
281 }
282 raw_blocks.setBAT(this);
283 }
284 } // end class BlockAllocationTableReader
285