1 | /* |
2 | * jDTAUS Core Utilities |
3 | * Copyright (C) 2005 Christian Schulte |
4 | * <cs@schulte.it> |
5 | * |
6 | * This library is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Lesser General Public |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2.1 of the License, or any later version. |
10 | * |
11 | * This library is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | * Lesser General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Lesser General Public |
17 | * License along with this library; if not, write to the Free Software |
18 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
19 | * |
20 | */ |
21 | package org.jdtaus.core.io.util; |
22 | |
23 | import java.io.IOException; |
24 | import java.io.InputStream; |
25 | import java.io.OutputStream; |
26 | import java.util.Iterator; |
27 | import java.util.Locale; |
28 | import java.util.Map; |
29 | import java.util.TreeMap; |
30 | import org.jdtaus.core.container.ContainerFactory; |
31 | import org.jdtaus.core.io.FileOperations; |
32 | import org.jdtaus.core.lang.spi.MemoryManager; |
33 | import org.jdtaus.core.logging.spi.Logger; |
34 | |
35 | /** |
36 | * Coalescing {@code FileOperations} cache. |
37 | * <p>This implementation implements a coalescing cache for |
38 | * {@code FileOperations} implementations. The cache is controlled by |
39 | * configuration property {@code blockSize}. By default property |
40 | * {@code blockSize} is initialized to {@code 2097152} leading to a cache |
41 | * size of 10 MB (multiplied by property {@code cacheSize} which defaults to |
42 | * {@code 5}). All memory is allocated during instantiation so that an |
43 | * {@code OutOfMemoryError} may be thrown when constructing the cache but not |
44 | * when working with the instance.</p> |
45 | * |
46 | * <p><b>Note:</b><br> |
47 | * This implementation is not thread-safe and concurrent changes to the |
48 | * underlying {@code FileOperations} implementation are not supported.</p> |
49 | * |
50 | * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> |
51 | * @version $JDTAUS: CoalescingFileOperations.java 8743 2012-10-07 03:06:20Z schulte $ |
52 | */ |
53 | public final class CoalescingFileOperations implements FlushableFileOperations |
54 | { |
55 | //--Dependencies------------------------------------------------------------ |
56 | |
57 | // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies |
58 | // This section is managed by jdtaus-container-mojo. |
59 | |
60 | /** |
61 | * Gets the configured <code>MemoryManager</code> implementation. |
62 | * |
63 | * @return The configured <code>MemoryManager</code> implementation. |
64 | */ |
65 | private MemoryManager getMemoryManager() |
66 | { |
67 | return (MemoryManager) ContainerFactory.getContainer(). |
68 | getDependency( this, "MemoryManager" ); |
69 | |
70 | } |
71 | |
72 | /** |
73 | * Gets the configured <code>Locale</code> implementation. |
74 | * |
75 | * @return The configured <code>Locale</code> implementation. |
76 | */ |
77 | private Locale getLocale() |
78 | { |
79 | return (Locale) ContainerFactory.getContainer(). |
80 | getDependency( this, "Locale" ); |
81 | |
82 | } |
83 | |
84 | /** |
85 | * Gets the configured <code>Logger</code> implementation. |
86 | * |
87 | * @return The configured <code>Logger</code> implementation. |
88 | */ |
89 | private Logger getLogger() |
90 | { |
91 | return (Logger) ContainerFactory.getContainer(). |
92 | getDependency( this, "Logger" ); |
93 | |
94 | } |
95 | |
96 | // </editor-fold>//GEN-END:jdtausDependencies |
97 | |
98 | //------------------------------------------------------------Dependencies-- |
99 | //--Properties-------------------------------------------------------------- |
100 | |
101 | // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausProperties |
102 | // This section is managed by jdtaus-container-mojo. |
103 | |
104 | /** |
105 | * Gets the value of property <code>defaultCacheBlocks</code>. |
106 | * |
107 | * @return Default number of cache blocks. |
108 | */ |
109 | private java.lang.Integer getDefaultCacheBlocks() |
110 | { |
111 | return (java.lang.Integer) ContainerFactory.getContainer(). |
112 | getProperty( this, "defaultCacheBlocks" ); |
113 | |
114 | } |
115 | |
116 | /** |
117 | * Gets the value of property <code>defaultBlockSize</code>. |
118 | * |
119 | * @return Default size of one cache block in byte. |
120 | */ |
121 | private java.lang.Integer getDefaultBlockSize() |
122 | { |
123 | return (java.lang.Integer) ContainerFactory.getContainer(). |
124 | getProperty( this, "defaultBlockSize" ); |
125 | |
126 | } |
127 | |
128 | // </editor-fold>//GEN-END:jdtausProperties |
129 | |
130 | //--------------------------------------------------------------Properties-- |
131 | //--FileOperations---------------------------------------------------------- |
132 | |
133 | public long getLength() throws IOException |
134 | { |
135 | this.assertNotClosed(); |
136 | |
137 | return this.getFileOperations().getLength(); |
138 | } |
139 | |
140 | public void setLength( final long newLength ) throws IOException |
141 | { |
142 | this.assertNotClosed(); |
143 | |
144 | // Update the length of any cache nodes involved in the operation. |
145 | final long oldLength = this.getLength(); |
146 | if ( newLength > oldLength ) |
147 | { |
148 | final long delta = newLength - oldLength; |
149 | |
150 | assert delta <= Integer.MAX_VALUE : |
151 | "Unexpected implementation limit reached."; |
152 | |
153 | final Node[] nodes = |
154 | this.getCacheNodesForLength( oldLength, (int) delta ); |
155 | |
156 | for ( int i = 0; i < nodes.length; i++ ) |
157 | { |
158 | final long startPos = nodes[i].block * this.getBlockSize(); |
159 | final long blockDelta = newLength - startPos; |
160 | |
161 | assert blockDelta <= Integer.MAX_VALUE : |
162 | "Unexpected implementation limit reached."; |
163 | |
164 | nodes[i].length = blockDelta >= this.getBlockSize() |
165 | ? this.getBlockSize() |
166 | : (int) blockDelta; |
167 | |
168 | } |
169 | } |
170 | else if ( newLength < oldLength ) |
171 | { |
172 | final long delta = oldLength - newLength; |
173 | |
174 | assert delta <= Integer.MAX_VALUE : |
175 | "Unexpected implementation limit reached."; |
176 | |
177 | final Node[] nodes = |
178 | this.getCacheNodesForLength( newLength, (int) delta ); |
179 | |
180 | for ( int i = 0; i < nodes.length; i++ ) |
181 | { |
182 | final long startPos = nodes[i].block * this.getBlockSize(); |
183 | if ( startPos > newLength ) |
184 | { // Discard the block. |
185 | this.root.remove( new Long( nodes[i].block ) ); |
186 | } |
187 | else |
188 | { // Update the blocks length. |
189 | final long blockDelta = newLength - startPos; |
190 | |
191 | assert blockDelta <= Integer.MAX_VALUE : |
192 | "Unexpected implementation limit reached."; |
193 | |
194 | nodes[i].length = blockDelta >= this.getBlockSize() |
195 | ? this.getBlockSize() |
196 | : (int) blockDelta; |
197 | |
198 | } |
199 | } |
200 | } |
201 | |
202 | this.getFileOperations().setLength( newLength ); |
203 | |
204 | if ( this.filePointer > newLength ) |
205 | { |
206 | this.filePointer = newLength; |
207 | } |
208 | } |
209 | |
210 | public long getFilePointer() throws IOException |
211 | { |
212 | this.assertNotClosed(); |
213 | |
214 | return this.filePointer; |
215 | } |
216 | |
217 | public void setFilePointer( final long pos ) throws IOException |
218 | { |
219 | this.assertNotClosed(); |
220 | |
221 | this.filePointer = pos; |
222 | } |
223 | |
224 | public int read( final byte[] buf, int off, int len ) throws IOException |
225 | { |
226 | if ( buf == null ) |
227 | { |
228 | throw new NullPointerException( "buf" ); |
229 | } |
230 | if ( off < 0 ) |
231 | { |
232 | throw new IndexOutOfBoundsException( Integer.toString( off ) ); |
233 | } |
234 | if ( len < 0 ) |
235 | { |
236 | throw new IndexOutOfBoundsException( Integer.toString( len ) ); |
237 | } |
238 | if ( off + len > buf.length ) |
239 | { |
240 | throw new IndexOutOfBoundsException( Integer.toString( off + len ) ); |
241 | } |
242 | |
243 | this.assertNotClosed(); |
244 | |
245 | int read = FileOperations.EOF; |
246 | |
247 | if ( len == 0 ) |
248 | { |
249 | read = 0; |
250 | } |
251 | else if ( this.filePointer < this.getLength() ) |
252 | { // End of file not reached. |
253 | final Node[] nodes = |
254 | this.getCacheNodesForLength( this.filePointer, len ); |
255 | |
256 | // Ensure cache holds the data of the involved blocks. |
257 | this.fillCache( nodes ); |
258 | |
259 | int copied = 0; |
260 | for ( int i = 0; i < nodes.length; i++ ) |
261 | { |
262 | if ( nodes[i].length == FileOperations.EOF ) |
263 | { // Skip any end of file nodes. |
264 | continue; |
265 | } |
266 | |
267 | if ( nodes[i].cacheIndex != Node.NO_CACHEINDEX ) |
268 | { // Node is associated with cache memory; cache is used. |
269 | |
270 | // Use the current file pointer as the starting index. |
271 | final long delta = |
272 | nodes[i].cacheIndex + |
273 | ( this.filePointer - nodes[i].block * |
274 | this.getBlockSize() ); |
275 | |
276 | assert delta <= Integer.MAX_VALUE : |
277 | "Unexpected implementation limit reached."; |
278 | |
279 | final int blockOffset = (int) delta; |
280 | final int blockDelta = nodes[i].length - |
281 | ( blockOffset - nodes[i].cacheIndex ); |
282 | |
283 | final int copyLength = len > blockDelta |
284 | ? blockDelta |
285 | : len; |
286 | |
287 | System.arraycopy( this.getCache(), blockOffset, buf, off, |
288 | copyLength ); |
289 | |
290 | off += copyLength; |
291 | len -= copyLength; |
292 | copied += copyLength; |
293 | this.filePointer += copyLength; |
294 | } |
295 | else |
296 | { // Node is not associated with cache memory; read directly. |
297 | this.getFileOperations().setFilePointer( this.filePointer ); |
298 | copied += this.getFileOperations().read( buf, off, len ); |
299 | this.filePointer += len; |
300 | |
301 | this.getLogger().debug( |
302 | this.getReadBypassesCacheMessage( |
303 | this.getLocale(), |
304 | new Integer( this.getBlockSize() ), |
305 | new Integer( this.getCacheBlocks() ), |
306 | new Integer( len ) ) ); |
307 | |
308 | |
309 | break; |
310 | } |
311 | } |
312 | |
313 | read = copied; |
314 | } |
315 | |
316 | return read; |
317 | } |
318 | |
319 | public void write( final byte[] buf, int off, int len ) throws IOException |
320 | { |
321 | if ( buf == null ) |
322 | { |
323 | throw new NullPointerException( "buf" ); |
324 | } |
325 | if ( off < 0 ) |
326 | { |
327 | throw new IndexOutOfBoundsException( Integer.toString( off ) ); |
328 | } |
329 | if ( len < 0 ) |
330 | { |
331 | throw new IndexOutOfBoundsException( Integer.toString( len ) ); |
332 | } |
333 | if ( off + len > buf.length ) |
334 | { |
335 | throw new IndexOutOfBoundsException( Integer.toString( off + len ) ); |
336 | } |
337 | |
338 | this.assertNotClosed(); |
339 | |
340 | if ( this.filePointer + len > this.getLength() ) |
341 | { // Expand the file of the backing instance. |
342 | this.setLength( this.filePointer + len ); |
343 | } |
344 | |
345 | final Node[] nodes = |
346 | this.getCacheNodesForLength( this.filePointer, len ); |
347 | |
348 | // Ensure cache holds the data of the involved blocks. |
349 | this.fillCache( nodes ); |
350 | |
351 | for ( int i = 0; i < nodes.length; i++ ) |
352 | { |
353 | // Check for correct file length update. |
354 | assert nodes[i].length != FileOperations.EOF : |
355 | "Unexpected cache state."; |
356 | |
357 | if ( nodes[i].cacheIndex != Node.NO_CACHEINDEX ) |
358 | { // Node is associated with cache memory; cache is used. |
359 | |
360 | // Use the current file pointer as the starting index. |
361 | final long delta = nodes[i].cacheIndex + |
362 | ( this.filePointer - nodes[i].block * |
363 | this.getBlockSize() ); |
364 | |
365 | assert delta <= Integer.MAX_VALUE : |
366 | "Unexpected implementation limit reached."; |
367 | |
368 | final int blockOffset = (int) delta; |
369 | final int blockDelta = nodes[i].length - |
370 | ( blockOffset - nodes[i].cacheIndex ); |
371 | |
372 | final int copyLength = len > blockDelta |
373 | ? blockDelta |
374 | : len; |
375 | |
376 | System.arraycopy( buf, off, this.getCache(), blockOffset, |
377 | copyLength ); |
378 | |
379 | off += copyLength; |
380 | len -= copyLength; |
381 | this.filePointer += copyLength; |
382 | nodes[i].dirty = true; |
383 | } |
384 | else |
385 | { // Node is not associated with cache memory; write out directly. |
386 | this.getFileOperations().setFilePointer( this.filePointer ); |
387 | this.getFileOperations().write( buf, off, len ); |
388 | this.filePointer += len; |
389 | |
390 | this.getLogger().debug( |
391 | this.getWriteBypassesCacheMessage( |
392 | this.getLocale(), |
393 | new Integer( this.getBlockSize() ), |
394 | new Integer( this.getCacheBlocks() ), |
395 | new Integer( len ) ) ); |
396 | |
397 | |
398 | break; |
399 | } |
400 | } |
401 | } |
402 | |
403 | public void read( final OutputStream out ) throws IOException |
404 | { |
405 | this.assertNotClosed(); |
406 | |
407 | this.getFileOperations().read( out ); |
408 | this.filePointer = this.getFileOperations().getFilePointer(); |
409 | } |
410 | |
411 | public void write( final InputStream in ) throws IOException |
412 | { |
413 | this.assertNotClosed(); |
414 | |
415 | this.getFileOperations().write( in ); |
416 | this.filePointer = this.getFileOperations().getFilePointer(); |
417 | } |
418 | |
419 | /** |
420 | * {@inheritDoc} |
421 | * Flushes the cache and closes the {@code FileOperations} implementation |
422 | * backing the instance. |
423 | * |
424 | * @throws IOException if flushing or closing the {@code FileOperations} |
425 | * implementation backing the instance fails, or if the instance already |
426 | * is closed. |
427 | */ |
428 | public void close() throws IOException |
429 | { |
430 | this.assertNotClosed(); |
431 | |
432 | this.flush(); |
433 | this.getFileOperations().close(); |
434 | this.closed = true; |
435 | } |
436 | |
437 | //----------------------------------------------------------FileOperations-- |
438 | //--FlushableFileOperations------------------------------------------------- |
439 | |
440 | /** |
441 | * {@inheritDoc} |
442 | * This method calls the {@code flush()} method of an underlying |
443 | * {@code FlushableFileOperations} implementation, if any. |
444 | * |
445 | * @throws IOException if writing any pending changes fails or if the |
446 | * instance is closed. |
447 | */ |
448 | public void flush() throws IOException |
449 | { |
450 | this.assertNotClosed(); |
451 | |
452 | this.defragmentCache(); |
453 | |
454 | long startPos = FileOperations.EOF; |
455 | int startIndex = FileOperations.EOF; |
456 | int length = FileOperations.EOF; |
457 | Node previous = null; |
458 | boolean dirty = false; |
459 | |
460 | for ( final Iterator it = this.root.entrySet().iterator(); |
461 | it.hasNext(); ) |
462 | { |
463 | final Map.Entry entry = (Map.Entry) it.next(); |
464 | final long block = ( (Long) entry.getKey() ).longValue(); |
465 | final Node current = (Node) entry.getValue(); |
466 | |
467 | // Skip any end of file nodes and nodes not associated with memory. |
468 | if ( current.length == FileOperations.EOF || |
469 | current.cacheIndex == Node.NO_CACHEINDEX ) |
470 | { |
471 | continue; |
472 | } |
473 | |
474 | assert current.block == block : "Unexpected cache state."; |
475 | |
476 | if ( previous == null ) |
477 | { // Start the first chunk. |
478 | previous = current; |
479 | startPos = current.block * this.getBlockSize(); |
480 | startIndex = current.cacheIndex; |
481 | length = current.length; |
482 | dirty = current.dirty; |
483 | } |
484 | else if ( current.block == previous.block + 1L ) |
485 | { // Expand the current chunk. |
486 | |
487 | assert current.cacheIndex == previous.cacheIndex + |
488 | this.getBlockSize() : |
489 | "Unexpected cache state."; |
490 | |
491 | previous = current; |
492 | length += current.length; |
493 | if ( !dirty ) |
494 | { |
495 | dirty = current.dirty; |
496 | } |
497 | } |
498 | else |
499 | { // Write out the current chunk and start a new one. |
500 | if ( dirty ) |
501 | { |
502 | this.getFileOperations().setFilePointer( startPos ); |
503 | this.getFileOperations().write( |
504 | this.getCache(), startIndex, length ); |
505 | |
506 | } |
507 | |
508 | previous = current; |
509 | startPos = current.block * this.getBlockSize(); |
510 | startIndex = current.cacheIndex; |
511 | length = current.length; |
512 | dirty = current.dirty; |
513 | } |
514 | } |
515 | |
516 | if ( dirty ) |
517 | { // Write the remaining chunk. |
518 | this.getFileOperations().setFilePointer( startPos ); |
519 | this.getFileOperations().write( |
520 | this.getCache(), startIndex, length ); |
521 | |
522 | } |
523 | |
524 | // Reset cache state. |
525 | for ( final Iterator it = this.root.entrySet().iterator(); |
526 | it.hasNext(); ) |
527 | { |
528 | final Map.Entry entry = (Map.Entry) it.next(); |
529 | final Node current = (Node) entry.getValue(); |
530 | |
531 | current.cacheIndex = Node.NO_CACHEINDEX; |
532 | current.dirty = false; |
533 | } |
534 | |
535 | this.nextCacheIndex = 0; |
536 | |
537 | if ( this.getFileOperations() instanceof FlushableFileOperations ) |
538 | { // Cache of the backing instance also needs to get flushed. |
539 | ( (FlushableFileOperations) this.getFileOperations() ).flush(); |
540 | } |
541 | } |
542 | |
543 | //-------------------------------------------------FlushableFileOperations-- |
544 | //--CoalescingFileOperations------------------------------------------------ |
545 | |
546 | /** Node describing a cache block. */ |
547 | private static final class Node |
548 | { |
549 | |
550 | private static final int NO_CACHEINDEX = Integer.MIN_VALUE; |
551 | |
552 | private Node() |
553 | { |
554 | super(); |
555 | } |
556 | |
557 | private long block; |
558 | |
559 | private int cacheIndex = NO_CACHEINDEX; |
560 | |
561 | private int length; |
562 | |
563 | private boolean dirty; |
564 | |
565 | } |
566 | |
567 | /** The {@code FileOperations} backing the instance. */ |
568 | private final FileOperations fileOperations; |
569 | |
570 | /** Cached blocks. */ |
571 | private byte[] cache; |
572 | |
573 | /** Second cache memory used during defragmentation. */ |
574 | private byte[] defragCache; |
575 | |
576 | /** Index of the next free cached block. */ |
577 | private int nextCacheIndex; |
578 | |
579 | /** Maps blocks to corresponding {@code Node}s. */ |
580 | private final Map root = new TreeMap(); |
581 | |
582 | /** File pointer. */ |
583 | private long filePointer; |
584 | |
585 | /** Caches the value returned by method {@code getFilePointerBlock}. */ |
586 | private long cachedFilePointerBlock = NO_FILEPOINTERBLOCK; |
587 | |
588 | private long cachedFilePointerBlockStart = FileOperations.EOF; |
589 | |
590 | private static final long NO_FILEPOINTERBLOCK = Long.MIN_VALUE; |
591 | |
592 | /** Flags the instance as beeing closed. */ |
593 | private boolean closed; |
594 | |
595 | /** The number of bytes occupied by one cache block. */ |
596 | private Integer blockSize; |
597 | |
598 | /** The number of cache blocks. */ |
599 | private Integer cacheBlocks; |
600 | |
601 | /** |
602 | * Creates a new {@code CoalescingFileOperations} instance taking the |
603 | * {@code FileOperations} backing the instance. |
604 | * |
605 | * @param fileOperations the {@code FileOperations} backing the instance. |
606 | * |
607 | * @throws NullPointerException if {@code fileOperations} is {@code null}. |
608 | * @throws IOException if reading fails. |
609 | */ |
610 | public CoalescingFileOperations( final FileOperations fileOperations ) |
611 | throws IOException |
612 | { |
613 | super(); |
614 | |
615 | if ( fileOperations == null ) |
616 | { |
617 | throw new NullPointerException( "fileOperations" ); |
618 | } |
619 | |
620 | this.fileOperations = fileOperations; |
621 | this.filePointer = fileOperations.getFilePointer(); |
622 | } |
623 | |
624 | /** |
625 | * Creates a new {@code CoalescingFileOperations} instance taking the |
626 | * {@code FileOperations} backing the instance and the number of bytes |
627 | * occupied by one cache block. |
628 | * |
629 | * @param fileOperations the {@code FileOperations} backing the instance. |
630 | * @param blockSize the number of bytes occupied by one cache block. |
631 | * |
632 | * @throws NullPointerException if {@code fileOperations} is {@code null}. |
633 | * @throws IOException if reading fails. |
634 | */ |
635 | public CoalescingFileOperations( final FileOperations fileOperations, |
636 | final int blockSize ) throws IOException |
637 | { |
638 | this( fileOperations ); |
639 | if ( blockSize > 0 ) |
640 | { |
641 | this.blockSize = new Integer( blockSize ); |
642 | } |
643 | } |
644 | |
645 | /** |
646 | * Creates a new {@code CoalescingFileOperations} instance taking the |
647 | * {@code FileOperations} backing the instance, the number of bytes |
648 | * occupied by one cache block and the number of cache blocks. |
649 | * |
650 | * @param fileOperations the {@code FileOperations} backing the instance. |
651 | * @param blockSize the number of bytes occupied by one cache block. |
652 | * @param cacheBlocks number of cache blocks. |
653 | * |
654 | * @throws NullPointerException if {@code fileOperations} is {@code null}. |
655 | * @throws IOException if reading fails. |
656 | */ |
657 | public CoalescingFileOperations( final FileOperations fileOperations, |
658 | final int blockSize, |
659 | final int cacheBlocks ) |
660 | throws IOException |
661 | { |
662 | this( fileOperations, blockSize ); |
663 | if ( cacheBlocks > 0 ) |
664 | { |
665 | this.cacheBlocks = new Integer( cacheBlocks ); |
666 | } |
667 | } |
668 | |
669 | /** |
670 | * Gets the {@code FileOperations} implementation operations are performed |
671 | * with. |
672 | * |
673 | * @return the {@code FileOperations} implementation operations are |
674 | * performed with. |
675 | */ |
676 | public FileOperations getFileOperations() |
677 | { |
678 | return this.fileOperations; |
679 | } |
680 | |
681 | /** |
682 | * Gets the number of bytes occupied by one cache block. |
683 | * |
684 | * @return the number of bytes occupied by one cache block. |
685 | */ |
686 | public int getBlockSize() |
687 | { |
688 | if ( this.blockSize == null ) |
689 | { |
690 | this.blockSize = this.getDefaultBlockSize(); |
691 | } |
692 | |
693 | return this.blockSize.intValue(); |
694 | } |
695 | |
696 | /** |
697 | * Gets the number of blocks in the cache. |
698 | * |
699 | * @return the number of blocks in the cache. |
700 | */ |
701 | public int getCacheBlocks() |
702 | { |
703 | if ( this.cacheBlocks == null ) |
704 | { |
705 | this.cacheBlocks = this.getDefaultCacheBlocks(); |
706 | } |
707 | |
708 | return this.cacheBlocks.intValue(); |
709 | } |
710 | |
711 | /** |
712 | * Gets the cache buffer. |
713 | * |
714 | * @return the cache buffer. |
715 | */ |
716 | private byte[] getCache() |
717 | { |
718 | if ( this.cache == null ) |
719 | { |
720 | this.cache = this.getMemoryManager().allocateBytes( |
721 | this.getBlockSize() * this.getCacheBlocks() ); |
722 | |
723 | } |
724 | |
725 | return this.cache; |
726 | } |
727 | |
728 | /** |
729 | * Gets the buffer used during defragmentation of the cache. |
730 | * |
731 | * @return the buffer used during defragmentation of the cache. |
732 | */ |
733 | private byte[] getDefragCache() |
734 | { |
735 | if ( this.defragCache == null ) |
736 | { |
737 | this.defragCache = this.getMemoryManager().allocateBytes( |
738 | this.getBlockSize() * this.getCacheBlocks() ); |
739 | |
740 | } |
741 | |
742 | return this.defragCache; |
743 | } |
744 | |
745 | /** |
746 | * Gets the block pointed to by a given file pointer value. |
747 | * |
748 | * @param filePointer the file pointer value for which to return the |
749 | * corresponding block. |
750 | * |
751 | * @return the block pointed to by {@code filePointer}. |
752 | */ |
753 | private long getFilePointerBlock( final long filePointer ) |
754 | { |
755 | if ( this.cachedFilePointerBlock == NO_FILEPOINTERBLOCK ) |
756 | { |
757 | this.cachedFilePointerBlock = |
758 | ( filePointer / this.getBlockSize() ) - |
759 | ( ( filePointer % this.getBlockSize() ) / this.getBlockSize() ); |
760 | |
761 | this.cachedFilePointerBlockStart = |
762 | this.cachedFilePointerBlock * this.getBlockSize(); |
763 | |
764 | } |
765 | else |
766 | { |
767 | if ( !( filePointer >= this.cachedFilePointerBlockStart && |
768 | filePointer <= this.cachedFilePointerBlockStart + |
769 | this.getBlockSize() ) ) |
770 | { |
771 | this.cachedFilePointerBlock = |
772 | ( filePointer / this.getBlockSize() ) - |
773 | ( ( filePointer % this.getBlockSize() ) / |
774 | this.getBlockSize() ); |
775 | |
776 | this.cachedFilePointerBlockStart = |
777 | this.cachedFilePointerBlock * this.getBlockSize(); |
778 | |
779 | } |
780 | } |
781 | |
782 | return this.cachedFilePointerBlock; |
783 | } |
784 | |
785 | /** |
786 | * Gets the cache nodes for all blocks involved in a read or write operation |
787 | * of a given length for a given file pointer value. |
788 | * |
789 | * @param filePointer the file pointer value to use for computing the |
790 | * number of involved blocks for a read or write operation of |
791 | * {@code length}. |
792 | * @param length the length of the operation to perform. |
793 | * |
794 | * @return an array of cache nodes for all blocks involved in the |
795 | * operation in the order corresponding to the operation's needs. |
796 | */ |
797 | private Node[] getCacheNodesForLength( final long filePointer, |
798 | final int length ) |
799 | { |
800 | final long startingBlock = this.getFilePointerBlock( filePointer ); |
801 | final long endingBlock = |
802 | this.getFilePointerBlock( filePointer + length ); |
803 | |
804 | assert endingBlock - startingBlock <= Integer.MAX_VALUE : |
805 | "Unexpected implementation limit reached."; |
806 | |
807 | final Node[] nodes = |
808 | new Node[ (int) ( endingBlock - startingBlock + 1L ) ]; |
809 | |
810 | if ( startingBlock == endingBlock ) |
811 | { |
812 | nodes[0] = this.getCacheNode( startingBlock ); |
813 | } |
814 | else |
815 | { |
816 | int i; |
817 | long block; |
818 | |
819 | for ( block = startingBlock, i = 0; block <= endingBlock; |
820 | block++, i++ ) |
821 | { |
822 | nodes[i] = this.getCacheNode( block ); |
823 | } |
824 | } |
825 | |
826 | return nodes; |
827 | } |
828 | |
829 | /** |
830 | * Fills the cache for a given set of cache nodes. |
831 | * <p>This method ensures that each given node gets associated with |
832 | * corresponding cache memory possibly flushing the cache before |
833 | * reading.</p> |
834 | * |
835 | * @param nodes the nodes to fill the cache for. |
836 | * |
837 | * @throws NullPointerException if {@code nodes} is {@code null}. |
838 | * @throws IOException if reading fails. |
839 | */ |
840 | private void fillCache( final Node[] nodes ) throws IOException |
841 | { |
842 | if ( nodes == null ) |
843 | { |
844 | throw new NullPointerException( "nodes" ); |
845 | } |
846 | |
847 | // Calculate the amount of bytes needed to be available in the cache |
848 | // and flush the cache if nodes would not fit. |
849 | long neededBytes = 0L; |
850 | for ( int i = nodes.length - 1; i >= 0; i-- ) |
851 | { |
852 | if ( nodes[i].cacheIndex == Node.NO_CACHEINDEX ) |
853 | { // Node's block needs to be read. |
854 | neededBytes += this.getBlockSize(); |
855 | } |
856 | } |
857 | |
858 | if ( this.nextCacheIndex + neededBytes > this.getCache().length ) |
859 | { // Cache cannot hold the needed blocks so needs flushing. |
860 | this.flush(); |
861 | } |
862 | |
863 | // Associate each node with cache memory for nodes not already |
864 | // associated with cache memory and fill these nodes' cache memory. |
865 | for ( int i = nodes.length - 1; i >= 0; i-- ) |
866 | { |
867 | if ( nodes[i].cacheIndex == Node.NO_CACHEINDEX && |
868 | this.nextCacheIndex < this.getCache().length ) |
869 | { // Node is not associated with any cache memory and can be read. |
870 | |
871 | // Update the length field of the node for the block checking |
872 | // for a possible end of file condition. |
873 | final long pos = nodes[i].block * this.getBlockSize(); |
874 | if ( pos > this.getLength() ) |
875 | { // Node is behind the end of the file. |
876 | nodes[i].length = FileOperations.EOF; |
877 | continue; |
878 | } |
879 | else if ( pos + this.getBlockSize() > this.getLength() ) |
880 | { |
881 | final long delta = this.getLength() - pos; |
882 | |
883 | assert delta <= Integer.MAX_VALUE : |
884 | "Unexpected implementation limit reached."; |
885 | |
886 | nodes[i].length = (int) delta; |
887 | } |
888 | else |
889 | { |
890 | nodes[i].length = this.getBlockSize(); |
891 | } |
892 | |
893 | // Associated the node with cache memory. |
894 | nodes[i].cacheIndex = this.nextCacheIndex; |
895 | this.nextCacheIndex += this.getBlockSize(); |
896 | |
897 | // Read the node's block into cache. |
898 | int read = FileOperations.EOF; |
899 | int totalRead = 0; |
900 | int toRead = nodes[i].length; |
901 | this.getFileOperations().setFilePointer( pos ); |
902 | |
903 | do |
904 | { |
905 | read = this.getFileOperations().read( |
906 | this.getCache(), nodes[i].cacheIndex + totalRead, |
907 | toRead ); |
908 | |
909 | assert read != FileOperations.EOF : |
910 | "Unexpected end of file."; |
911 | |
912 | totalRead += read; |
913 | toRead -= read; |
914 | |
915 | } |
916 | while ( totalRead < nodes[i].length ); |
917 | } |
918 | } |
919 | } |
920 | |
921 | /** Defragments the cache. */ |
922 | private void defragmentCache() |
923 | { |
924 | int defragIndex = 0; |
925 | |
926 | // Step through the cached blocks and defragment the cache. |
927 | for ( final Iterator it = this.root.entrySet().iterator(); |
928 | it.hasNext(); ) |
929 | { |
930 | final Map.Entry entry = (Map.Entry) it.next(); |
931 | final long block = ( (Long) entry.getKey() ).longValue(); |
932 | final Node current = (Node) entry.getValue(); |
933 | |
934 | // Skip any end of file nodes and nodes not associated with memory. |
935 | if ( current.length == FileOperations.EOF || |
936 | current.cacheIndex == Node.NO_CACHEINDEX ) |
937 | { |
938 | continue; |
939 | } |
940 | |
941 | assert current.block == block : "Unexpected cache state."; |
942 | |
943 | System.arraycopy( this.getCache(), current.cacheIndex, |
944 | this.getDefragCache(), defragIndex, |
945 | this.getBlockSize() ); |
946 | |
947 | current.cacheIndex = defragIndex; |
948 | defragIndex += this.getBlockSize(); |
949 | } |
950 | |
951 | System.arraycopy( this.getDefragCache(), 0, this.getCache(), 0, |
952 | this.getCache().length ); |
953 | |
954 | } |
955 | |
956 | /** |
957 | * Gets the cache node for a given block. |
958 | * |
959 | * @param block the block to return the corresponding cache node for. |
960 | * |
961 | * @return the cache node for {@code block}. |
962 | */ |
963 | private Node getCacheNode( final long block ) |
964 | { |
965 | final Long key = new Long( block ); |
966 | Node node = (Node) this.root.get( key ); |
967 | if ( node == null ) |
968 | { |
969 | node = new Node(); |
970 | node.block = block; |
971 | this.root.put( key, node ); |
972 | } |
973 | |
974 | return node; |
975 | } |
976 | |
977 | /** |
978 | * Checks that the instance is not closed. |
979 | * |
980 | * @throws IOException if the instance is closed. |
981 | */ |
982 | private void assertNotClosed() throws IOException |
983 | { |
984 | if ( this.closed ) |
985 | { |
986 | throw new IOException( this.getAlreadyClosedMessage( |
987 | this.getLocale() ) ); |
988 | |
989 | } |
990 | } |
991 | |
992 | //------------------------------------------------CoalescingFileOperations-- |
993 | //--Messages---------------------------------------------------------------- |
994 | |
995 | // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausMessages |
996 | // This section is managed by jdtaus-container-mojo. |
997 | |
998 | /** |
999 | * Gets the text of message <code>readBypassesCache</code>. |
1000 | * <blockquote><pre>Eine Lese-Operation umging den Cache. Cache zu klein dimensioniert. Aktuelle Blockgröße ist {0,number} und Cache umfaßt {1,number} Blöcke. {2,number} Bytes konnten nicht zwischengespeichert werden.</pre></blockquote> |
1001 | * <blockquote><pre>A read operation bypassed the cache. Consider increasing the cache. Current block size is {0,number} and current number of cache blocks is {1,number}. {2,number} bytes could not be cached.</pre></blockquote> |
1002 | * |
1003 | * @param locale The locale of the message instance to return. |
1004 | * @param blockSize The current block size in use. |
1005 | * @param cacheBlocks The current number of blocks in use. |
1006 | * @param uncachedBytes The number of bytes bypassing caching. |
1007 | * |
1008 | * @return Information about a misconfigured cache. |
1009 | */ |
1010 | private String getReadBypassesCacheMessage( final Locale locale, |
1011 | final java.lang.Number blockSize, |
1012 | final java.lang.Number cacheBlocks, |
1013 | final java.lang.Number uncachedBytes ) |
1014 | { |
1015 | return ContainerFactory.getContainer(). |
1016 | getMessage( this, "readBypassesCache", locale, |
1017 | new Object[] |
1018 | { |
1019 | blockSize, |
1020 | cacheBlocks, |
1021 | uncachedBytes |
1022 | }); |
1023 | |
1024 | } |
1025 | |
1026 | /** |
1027 | * Gets the text of message <code>writeBypassesCache</code>. |
1028 | * <blockquote><pre>Eine Schreib-Operation umging den Cache. Cache zu klein dimensioniert. Aktuelle Blockgröße ist {0,number} und Cache umfaßt {1,number} Blöcke. {2,number} Bytes konnten nicht zwischengespeichert werden.</pre></blockquote> |
1029 | * <blockquote><pre>A write operation bypassed the cache. Consider increasing the cache. Current block size is {0,number} and current number of cache blocks is {1,number}. {2,number} bytes could not be cached.</pre></blockquote> |
1030 | * |
1031 | * @param locale The locale of the message instance to return. |
1032 | * @param blockSize The current block size in use. |
1033 | * @param cacheBlocks The current number of blocks in use. |
1034 | * @param uncachedBytes The number of bytes bypassing caching. |
1035 | * |
1036 | * @return Information about a misconfigured cache. |
1037 | */ |
1038 | private String getWriteBypassesCacheMessage( final Locale locale, |
1039 | final java.lang.Number blockSize, |
1040 | final java.lang.Number cacheBlocks, |
1041 | final java.lang.Number uncachedBytes ) |
1042 | { |
1043 | return ContainerFactory.getContainer(). |
1044 | getMessage( this, "writeBypassesCache", locale, |
1045 | new Object[] |
1046 | { |
1047 | blockSize, |
1048 | cacheBlocks, |
1049 | uncachedBytes |
1050 | }); |
1051 | |
1052 | } |
1053 | |
1054 | /** |
1055 | * Gets the text of message <code>alreadyClosed</code>. |
1056 | * <blockquote><pre>Instanz geschlossen - keine E/A-Operationen möglich.</pre></blockquote> |
1057 | * <blockquote><pre>Instance closed - cannot perform I/O.</pre></blockquote> |
1058 | * |
1059 | * @param locale The locale of the message instance to return. |
1060 | * |
1061 | * @return Message stating that an instance is already closed. |
1062 | */ |
1063 | private String getAlreadyClosedMessage( final Locale locale ) |
1064 | { |
1065 | return ContainerFactory.getContainer(). |
1066 | getMessage( this, "alreadyClosed", locale, null ); |
1067 | |
1068 | } |
1069 | |
1070 | // </editor-fold>//GEN-END:jdtausMessages |
1071 | |
1072 | //----------------------------------------------------------------Messages-- |
1073 | } |