%line | %branch | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
org.apache.jcs.auxiliary.disk.block.BlockDisk |
|
|
1 | package org.apache.jcs.auxiliary.disk.block; |
|
2 | ||
3 | /* |
|
4 | * Licensed to the Apache Software Foundation (ASF) under one |
|
5 | * or more contributor license agreements. See the NOTICE file |
|
6 | * distributed with this work for additional information |
|
7 | * regarding copyright ownership. The ASF licenses this file |
|
8 | * to you under the Apache License, Version 2.0 (the |
|
9 | * "License"); you may not use this file except in compliance |
|
10 | * with the License. You may obtain a copy of the License at |
|
11 | * |
|
12 | * http://www.apache.org/licenses/LICENSE-2.0 |
|
13 | * |
|
14 | * Unless required by applicable law or agreed to in writing, |
|
15 | * software distributed under the License is distributed on an |
|
16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
|
17 | * KIND, either express or implied. See the License for the |
|
18 | * specific language governing permissions and limitations |
|
19 | * under the License. |
|
20 | */ |
|
21 | ||
22 | import java.io.File; |
|
23 | import java.io.FileNotFoundException; |
|
24 | import java.io.IOException; |
|
25 | import java.io.RandomAccessFile; |
|
26 | import java.io.Serializable; |
|
27 | ||
28 | import org.apache.commons.logging.Log; |
|
29 | import org.apache.commons.logging.LogFactory; |
|
30 | import org.apache.jcs.utils.serialization.StandardSerializer; |
|
31 | import org.apache.jcs.utils.struct.SingleLinkedList; |
|
32 | ||
33 | /** |
|
34 | * This class manages reading an writing data to disk. When asked to write a value, it returns a |
|
35 | * block array. It can read an object from the block numbers in a byte array. |
|
36 | * <p> |
|
37 | * @author Aaron Smuts |
|
38 | */ |
|
39 | public class BlockDisk |
|
40 | { |
|
41 | /** The logger */ |
|
42 | 64 | private static final Log log = LogFactory.getLog( BlockDisk.class ); |
43 | ||
44 | /** The size of the header that indicates the amount of data stored in an occupied block. */ |
|
45 | public static final byte HEADER_SIZE_BYTES = 4; |
|
46 | ||
47 | /** defaults to 4kb */ |
|
48 | private static final int DEFAULT_BLOCK_SIZE_BYTES = 4 * 1024; |
|
49 | ||
50 | /** Size of the blocks */ |
|
51 | 327 | private int blockSizeBytes = DEFAULT_BLOCK_SIZE_BYTES; |
52 | ||
53 | /** |
|
54 | * the total number of blocks that have been used. If there are no free, we will use this to |
|
55 | * calculate the position of the next block. |
|
56 | */ |
|
57 | 327 | private int numberOfBlocks = 0; |
58 | ||
59 | /** Empty blocks that can be reused. */ |
|
60 | 327 | private SingleLinkedList emptyBlocks = new SingleLinkedList(); |
61 | ||
62 | /** Handles serializing the objects */ |
|
63 | 32 | private static final StandardSerializer SERIALIZER = new StandardSerializer(); |
64 | ||
65 | /** Location of the spot on disk */ |
|
66 | private final String filepath; |
|
67 | ||
68 | /** The file handle. */ |
|
69 | private RandomAccessFile raf; |
|
70 | ||
71 | /** How many bytes have we put to disk */ |
|
72 | 327 | private long putBytes = 0; |
73 | ||
74 | /** How many items have we put to disk */ |
|
75 | 327 | private long putCount = 0; |
76 | ||
77 | /** |
|
78 | * Constructor for the Disk object |
|
79 | * <p> |
|
80 | * @param file |
|
81 | * @exception FileNotFoundException |
|
82 | */ |
|
83 | public BlockDisk( File file ) |
|
84 | throws FileNotFoundException |
|
85 | { |
|
86 | 287 | this( file, DEFAULT_BLOCK_SIZE_BYTES ); |
87 | 287 | if ( log.isInfoEnabled() ) |
88 | { |
|
89 | 287 | log.info( "Used default block size [" + DEFAULT_BLOCK_SIZE_BYTES + "]" ); |
90 | } |
|
91 | ||
92 | 287 | } |
93 | ||
94 | /** |
|
95 | * Creates the file and set the block size in bytes. |
|
96 | * <p> |
|
97 | * @param file |
|
98 | * @param blockSizeBytes |
|
99 | * @throws FileNotFoundException |
|
100 | */ |
|
101 | public BlockDisk( File file, int blockSizeBytes ) |
|
102 | throws FileNotFoundException |
|
103 | 327 | { |
104 | 327 | this.filepath = file.getAbsolutePath(); |
105 | 327 | raf = new RandomAccessFile( filepath, "rw" ); |
106 | ||
107 | 327 | if ( log.isInfoEnabled() ) |
108 | { |
|
109 | 327 | log.info( "Constructing BlockDisk, blockSizeBytes [" + blockSizeBytes + "]" ); |
110 | } |
|
111 | 327 | this.blockSizeBytes = blockSizeBytes; |
112 | 327 | } |
113 | ||
114 | /** |
|
115 | * This writes an object to disk and returns the blocks it was stored in. |
|
116 | * <p> |
|
117 | * The program flow is as follows: |
|
118 | * <ol> |
|
119 | * <li>Serialize the object.</li> |
|
120 | * <li>Detemine the number of blocks needed.</li> |
|
121 | * <li>Look for free blocks in the emptyBlock list.</li> |
|
122 | * <li>If there were not enough in the empty list. Take the nextBlock and increment it.</li> |
|
123 | * <li>If the data will not fit in one block, create sub arrays.</li> |
|
124 | * <li>Write the subarrays to disk.</li> |
|
125 | * <li>If the process fails we should decrement the block count if we took from it.</li> |
|
126 | * </ol> |
|
127 | * @param object |
|
128 | * @return the blocks we used. |
|
129 | * @throws IOException |
|
130 | */ |
|
131 | protected int[] write( Serializable object ) |
|
132 | throws IOException |
|
133 | { |
|
134 | // serialize the object |
|
135 | 69623 | byte[] data = SERIALIZER.serialize( object ); |
136 | ||
137 | 69627 | this.addToPutBytes( data.length ); |
138 | 69624 | this.incrementPutCount(); |
139 | ||
140 | // figure out how many blocks we need. |
|
141 | 69626 | int numBlocksNeeded = calculateTheNumberOfBlocksNeeded( data ); |
142 | ||
143 | 69627 | int[] blocks = new class="keyword">int[numBlocksNeeded]; |
144 | ||
145 | // get them from the empty list or take the next one |
|
146 | 144054 | for ( short i = 0; i < numBlocksNeeded; i++ ) |
147 | { |
|
148 | 74435 | Integer emptyBlock = (Integer) emptyBlocks.takeFirst(); |
149 | 74435 | if ( emptyBlock != null ) |
150 | { |
|
151 | 32230 | blocks[i] = emptyBlock.intValue(); |
152 | 32230 | } |
153 | else |
|
154 | { |
|
155 | 42204 | blocks[i] = takeNextBlock(); |
156 | } |
|
157 | } |
|
158 | ||
159 | // get the individual sub arrays. |
|
160 | 69626 | byte[][] chunks = getBlockChunks( data, numBlocksNeeded ); |
161 | ||
162 | // write the blocks |
|
163 | 144052 | for ( byte i = 0; i < numBlocksNeeded; i++ ) |
164 | { |
|
165 | 74432 | int position = calculateByteOffsetForBlock( blocks[i] ); |
166 | 74434 | write( position, chunks[i] ); |
167 | } |
|
168 | ||
169 | 69623 | return blocks; |
170 | } |
|
171 | ||
172 | /** |
|
173 | * Return the amount to put in each block. Fill them all the way, minus the header. |
|
174 | * <p> |
|
175 | * @param complete |
|
176 | * @param numBlocksNeeded |
|
177 | * @return byte[][] |
|
178 | */ |
|
179 | protected byte[][] getBlockChunks( byte[] complete, int numBlocksNeeded ) |
|
180 | { |
|
181 | 69628 | byte[][] chunks = new byte[numBlocksNeeded][]; |
182 | ||
183 | 69628 | if ( numBlocksNeeded == 1 ) |
184 | { |
|
185 | 68020 | chunks[0] = complete; |
186 | 68020 | } |
187 | else |
|
188 | { |
|
189 | 1608 | int maxChunkSize = this.blockSizeBytes - HEADER_SIZE_BYTES; |
190 | 1608 | int totalBytes = complete.length; |
191 | 1608 | int totalUsed = 0; |
192 | 8024 | for ( short i = 0; i < numBlocksNeeded; i++ ) |
193 | { |
|
194 | // use the max that can be written to a block or whatever is left in the original |
|
195 | // array |
|
196 | 6416 | int chunkSize = Math.min( totalUsed + maxChunkSize, totalBytes - totalUsed ); |
197 | 6416 | byte[] chunk = new byte[chunkSize]; |
198 | // copy from the used position to the chunk size on the complete array to the chunk |
|
199 | // array. |
|
200 | 6416 | System.arraycopy( complete, totalUsed, chunk, 0, chunkSize ); |
201 | 6416 | chunks[i] = chunk; |
202 | 6416 | totalUsed += chunkSize; |
203 | } |
|
204 | } |
|
205 | ||
206 | 69628 | return chunks; |
207 | } |
|
208 | ||
209 | /** |
|
210 | * Writes the given byte array to the Disk at the specified position. |
|
211 | * <p> |
|
212 | * @param position |
|
213 | * @param data |
|
214 | * @return true if we wrote successfully |
|
215 | * @throws IOException |
|
216 | */ |
|
217 | private boolean write( long position, byte[] data ) |
|
218 | throws IOException |
|
219 | { |
|
220 | 74436 | synchronized ( this ) |
221 | { |
|
222 | 74434 | raf.seek( position ); |
223 | 74435 | raf.writeInt( data.length ); |
224 | 74434 | raf.write( data, 0, data.length ); |
225 | 74429 | } |
226 | 74435 | return true; |
227 | } |
|
228 | ||
229 | /** |
|
230 | * Reads an object that is located in the specified blocks. |
|
231 | * <p> |
|
232 | * @param blockNumbers |
|
233 | * @return Serializable |
|
234 | * @throws IOException |
|
235 | * @throws ClassNotFoundException |
|
236 | */ |
|
237 | protected Serializable read( int[] blockNumbers ) |
|
238 | throws IOException, ClassNotFoundException |
|
239 | { |
|
240 | 38064 | byte[] data = null; |
241 | ||
242 | 38060 | if ( blockNumbers.length == 1 ) |
243 | { |
|
244 | 36463 | data = readBlock( blockNumbers[0] ); |
245 | 36460 | } |
246 | else |
|
247 | { |
|
248 | 1600 | data = new byte[0]; |
249 | // get all the blocks into data |
|
250 | 8000 | for ( short i = 0; i < blockNumbers.length; i++ ) |
251 | { |
|
252 | 6400 | byte[] chunk = readBlock( blockNumbers[i] ); |
253 | 6400 | byte[] newTotal = new byte[data.length + chunk.length]; |
254 | // copy data into the new array |
|
255 | 6400 | System.arraycopy( data, 0, newTotal, 0, data.length ); |
256 | // copyt the chunk into the new array |
|
257 | 6400 | System.arraycopy( chunk, 0, newTotal, data.length, chunk.length ); |
258 | // swap the new and old. |
|
259 | 6400 | data = newTotal; |
260 | } |
|
261 | } |
|
262 | ||
263 | 38060 | return (Serializable) SERIALIZER.deSerialize( data ); |
264 | } |
|
265 | ||
266 | /** |
|
267 | * This reads the occupied data in a block. |
|
268 | * <p> |
|
269 | * The first four bytes of the record should tell us how long it is. The data is read into a |
|
270 | * byte array and then an object is constructed from the byte array. |
|
271 | * <p> |
|
272 | * @return byte[] |
|
273 | * @param block |
|
274 | * @throws IOException |
|
275 | */ |
|
276 | private byte[] readBlock( int block ) |
|
277 | throws IOException |
|
278 | { |
|
279 | 42863 | byte[] data = null; |
280 | 42864 | int datalen = 0; |
281 | 42864 | synchronized ( this ) |
282 | { |
|
283 | 42864 | String message = null; |
284 | 42864 | boolean corrupted = false; |
285 | 42863 | long fileLength = raf.length(); |
286 | ||
287 | 42863 | int position = calculateByteOffsetForBlock( block ); |
288 | 42863 | if ( position > fileLength ) |
289 | { |
|
290 | 0 | corrupted = true; |
291 | 0 | message = "Record " + position + " starts past EOF."; |
292 | 0 | } |
293 | else |
|
294 | { |
|
295 | 42863 | raf.seek( position ); |
296 | 42863 | datalen = raf.readInt(); |
297 | 42861 | if ( position + datalen > fileLength ) |
298 | { |
|
299 | 0 | corrupted = true; |
300 | 0 | message = "Record " + position + " exceeds file length."; |
301 | } |
|
302 | } |
|
303 | ||
304 | 42862 | if ( corrupted ) |
305 | { |
|
306 | 0 | log.warn( "\n The file is corrupt: " + "\n " + message ); |
307 | 0 | throw new IOException( "The File Is Corrupt, need to reset" ); |
308 | } |
|
309 | ||
310 | 42862 | raf.readFully( data = new byte[datalen] ); |
311 | 42859 | } |
312 | 42859 | return data; |
313 | } |
|
314 | ||
315 | /** |
|
316 | * Add these blocks to the emptyBlock list. |
|
317 | * <p> |
|
318 | * @param blocksToFree |
|
319 | */ |
|
320 | protected void freeBlocks( int[] blocksToFree ) |
|
321 | { |
|
322 | 39162 | if ( blocksToFree != null ) |
323 | { |
|
324 | 78322 | for ( short i = 0; i < blocksToFree.length; i++ ) |
325 | { |
|
326 | 39162 | emptyBlocks.addLast( new Integer( blocksToFree[i] ) ); |
327 | } |
|
328 | } |
|
329 | 39163 | } |
330 | ||
331 | /** |
|
332 | * Add to to total put size. |
|
333 | * <p> |
|
334 | * @param length |
|
335 | */ |
|
336 | private synchronized void addToPutBytes( long length ) |
|
337 | { |
|
338 | 69628 | this.putBytes += length; |
339 | 69628 | } |
340 | ||
341 | /** |
|
342 | * Thread safe increment. |
|
343 | */ |
|
344 | private synchronized void incrementPutCount() |
|
345 | { |
|
346 | 69628 | this.putCount++; |
347 | 69628 | } |
348 | ||
349 | /** |
|
350 | * Returns the current number and adds one. |
|
351 | * <p> |
|
352 | * @return the block number to use. |
|
353 | */ |
|
354 | private synchronized int takeNextBlock() |
|
355 | { |
|
356 | 42206 | return this.numberOfBlocks++; |
357 | } |
|
358 | ||
359 | /** |
|
360 | * Calcuates the file offset for a particular block. |
|
361 | * <p> |
|
362 | * @param block |
|
363 | * @return the offset for this block |
|
364 | */ |
|
365 | protected int calculateByteOffsetForBlock( class="keyword">int block ) |
|
366 | { |
|
367 | 117272 | return block * blockSizeBytes; |
368 | } |
|
369 | ||
370 | /** |
|
371 | * The number of blocks needed. |
|
372 | * <p> |
|
373 | * @param data |
|
374 | * @return the number of blocks needed to store the byte array |
|
375 | */ |
|
376 | protected int calculateTheNumberOfBlocksNeeded( byte[] data ) |
|
377 | { |
|
378 | 69636 | int dataLength = data.length; |
379 | ||
380 | 69636 | int oneBlock = blockSizeBytes - HEADER_SIZE_BYTES; |
381 | ||
382 | // takes care of 0 = HEADER_SIZE_BYTES + blockSizeBytes |
|
383 | 69635 | if ( dataLength <= oneBlock ) |
384 | { |
|
385 | 68016 | return 1; |
386 | } |
|
387 | ||
388 | 1616 | int dividend = dataLength / oneBlock; |
389 | ||
390 | 1616 | if ( dataLength % oneBlock != 0 ) |
391 | { |
|
392 | 1608 | dividend++; |
393 | } |
|
394 | 1616 | return dividend; |
395 | } |
|
396 | ||
397 | /** |
|
398 | * Returns the raf length. |
|
399 | * <p> |
|
400 | * @return the size of the file. |
|
401 | * @exception IOException |
|
402 | */ |
|
403 | protected long length() |
|
404 | throws IOException |
|
405 | { |
|
406 | 7071 | synchronized ( this ) |
407 | { |
|
408 | 7071 | return raf.length(); |
409 | 0 | } |
410 | } |
|
411 | ||
412 | /** |
|
413 | * Closes the raf. |
|
414 | * <p> |
|
415 | * @exception IOException |
|
416 | */ |
|
417 | protected synchronized void close() |
|
418 | throws IOException |
|
419 | { |
|
420 | 270 | raf.close(); |
421 | 270 | } |
422 | ||
423 | /** |
|
424 | * Returns the serialized form of the given object in a byte array. |
|
425 | * <p> |
|
426 | * Use the Serilizer abstraction layer. |
|
427 | * <p> |
|
428 | * @return a byte array of the serialized object. |
|
429 | * @param obj |
|
430 | * @exception IOException |
|
431 | */ |
|
432 | protected static byte[] serialize( Serializable obj ) |
|
433 | throws IOException |
|
434 | { |
|
435 | 0 | return SERIALIZER.serialize( obj ); |
436 | } |
|
437 | ||
438 | /** |
|
439 | * @return Returns the numberOfBlocks. |
|
440 | */ |
|
441 | protected int getNumberOfBlocks() |
|
442 | { |
|
443 | 7103 | return numberOfBlocks; |
444 | } |
|
445 | ||
446 | /** |
|
447 | * @return Returns the blockSizeBytes. |
|
448 | */ |
|
449 | protected int getBlockSizeBytes() |
|
450 | { |
|
451 | 7103 | return blockSizeBytes; |
452 | } |
|
453 | ||
454 | /** |
|
455 | * @return Returns the average size of the an element inserted. |
|
456 | */ |
|
457 | protected long getAveragePutSizeBytes() |
|
458 | { |
|
459 | 7071 | if ( this.putCount == 0 ) |
460 | { |
|
461 | 0 | return 0; |
462 | } |
|
463 | 7071 | return this.putBytes / class="keyword">this.putCount; |
464 | } |
|
465 | ||
466 | /** |
|
467 | * @return Returns the number of empty blocks. |
|
468 | */ |
|
469 | protected int getEmptyBlocks() |
|
470 | { |
|
471 | 7071 | return this.emptyBlocks.size(); |
472 | } |
|
473 | ||
474 | /** |
|
475 | * For debugging only. |
|
476 | * <p> |
|
477 | * @return String with details. |
|
478 | */ |
|
479 | public String toString() |
|
480 | { |
|
481 | 32 | StringBuffer buf = new StringBuffer(); |
482 | 32 | buf.append( "\nBlock Disk " ); |
483 | 32 | buf.append( "\n Filepath [" + filepath + "]" ); |
484 | 32 | buf.append( "\n NumberOfBlocks [" + getNumberOfBlocks() + "]" ); |
485 | 32 | buf.append( "\n BlockSizeBytes [" + getBlockSizeBytes() + "]" ); |
486 | 32 | buf.append( "\n Put Bytes [" + this.putBytes + "]" ); |
487 | 32 | buf.append( "\n Put Count [" + this.putCount + "]" ); |
488 | 32 | buf.append( "\n Average Size [" + getAveragePutSizeBytes() + "]" ); |
489 | 32 | buf.append( "\n Empty Blocks [" + this.getEmptyBlocks() + "]" ); |
490 | try |
|
491 | { |
|
492 | 32 | buf.append( "\n Length [" + length() + "]" ); |
493 | } |
|
494 | 0 | catch ( IOException e ) |
495 | { |
|
496 | // swallow |
|
497 | 32 | } |
498 | 32 | return buf.toString(); |
499 | } |
|
500 | } |
This report is generated by jcoverage, Maven and Maven JCoverage Plugin. |