EMMA Coverage Report (generated Wed Oct 03 05:00:03 CEST 2012)
[all classes][org.jdtaus.core.io.util]

COVERAGE SUMMARY FOR SOURCE FILE [CoalescingFileOperations.java]

nameclass, %method, %block, %line, %
CoalescingFileOperations.java100% (2/2)88%  (30/34)87%  (1304/1491)89%  (248.7/280)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class CoalescingFileOperations100% (1/1)88%  (29/33)87%  (1298/1485)89%  (246.7/278)
CoalescingFileOperations (FileOperations, int, int): void 0%   (0/1)0%   (0/13)0%   (0/4)
close (): void 0%   (0/1)0%   (0/11)0%   (0/5)
getAlreadyClosedMessage (Locale): String 0%   (0/1)0%   (0/7)0%   (0/1)
read (OutputStream): void 0%   (0/1)0%   (0/12)0%   (0/4)
assertNotClosed (): void 100% (1/1)33%  (4/12)67%  (2/3)
<static initializer> 100% (1/1)80%  (12/15)80%  (0.8/1)
CoalescingFileOperations (FileOperations): void 100% (1/1)82%  (23/28)89%  (8/9)
write (byte [], int, int): void 100% (1/1)83%  (176/211)86%  (28.4/33)
read (byte [], int, int): int 100% (1/1)85%  (178/209)86%  (32.8/38)
setLength (long): void 100% (1/1)87%  (156/179)95%  (25.6/27)
fillCache (CoalescingFileOperations$Node []): void 100% (1/1)92%  (171/186)95%  (30.5/32)
getCacheNodesForLength (long, int): CoalescingFileOperations$Node [] 100% (1/1)92%  (60/65)98%  (9.8/10)
flush (): void 100% (1/1)93%  (179/193)96%  (46.2/48)
defragmentCache (): void 100% (1/1)93%  (69/74)97%  (13.6/14)
CoalescingFileOperations (FileOperations, int): void 100% (1/1)100% (12/12)100% (4/4)
getBlockSize (): int 100% (1/1)100% (11/11)100% (3/3)
getCache (): byte [] 100% (1/1)100% (16/16)100% (3/3)
getCacheBlocks (): int 100% (1/1)100% (11/11)100% (3/3)
getCacheNode (long): CoalescingFileOperations$Node 100% (1/1)100% (29/29)100% (7/7)
getDefaultBlockSize (): Integer 100% (1/1)100% (6/6)100% (1/1)
getDefaultCacheBlocks (): Integer 100% (1/1)100% (6/6)100% (1/1)
getDefragCache (): byte [] 100% (1/1)100% (16/16)100% (3/3)
getFileOperations (): FileOperations 100% (1/1)100% (3/3)100% (1/1)
getFilePointer (): long 100% (1/1)100% (5/5)100% (2/2)
getFilePointerBlock (long): long 100% (1/1)100% (73/73)100% (7/7)
getLength (): long 100% (1/1)100% (6/6)100% (2/2)
getLocale (): Locale 100% (1/1)100% (6/6)100% (1/1)
getLogger (): Logger 100% (1/1)100% (6/6)100% (1/1)
getMemoryManager (): MemoryManager 100% (1/1)100% (6/6)100% (1/1)
getReadBypassesCacheMessage (Locale, Number, Number, Number): String 100% (1/1)100% (20/20)100% (1/1)
getWriteBypassesCacheMessage (Locale, Number, Number, Number): String 100% (1/1)100% (20/20)100% (1/1)
setFilePointer (long): void 100% (1/1)100% (6/6)100% (3/3)
write (InputStream): void 100% (1/1)100% (12/12)100% (4/4)
     
class CoalescingFileOperations$Node100% (1/1)100% (1/1)100% (6/6)100% (2/2)
CoalescingFileOperations$Node (): void 100% (1/1)100% (6/6)100% (2/2)

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 */
21package org.jdtaus.core.io.util;
22 
23import java.io.IOException;
24import java.io.InputStream;
25import java.io.OutputStream;
26import java.util.Iterator;
27import java.util.Locale;
28import java.util.Map;
29import java.util.TreeMap;
30import org.jdtaus.core.container.ContainerFactory;
31import org.jdtaus.core.io.FileOperations;
32import org.jdtaus.core.lang.spi.MemoryManager;
33import 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 8641 2012-09-27 06:45:17Z schulte $
52 */
53public 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 ( Iterator it = this.root.entrySet().iterator(); it.hasNext(); )
461        {
462            final Map.Entry entry = (Map.Entry) it.next();
463            final long block = ( (Long) entry.getKey() ).longValue();
464            final Node current = (Node) entry.getValue();
465 
466            // Skip any end of file nodes and nodes not associated with memory.
467            if ( current.length == FileOperations.EOF ||
468                 current.cacheIndex == Node.NO_CACHEINDEX )
469            {
470                continue;
471            }
472 
473            assert current.block == block : "Unexpected cache state.";
474 
475            if ( previous == null )
476            { // Start the first chunk.
477                previous = current;
478                startPos = current.block * this.getBlockSize();
479                startIndex = current.cacheIndex;
480                length = current.length;
481                dirty = current.dirty;
482            }
483            else if ( current.block == previous.block + 1L )
484            { // Expand the current chunk.
485 
486                assert current.cacheIndex == previous.cacheIndex +
487                                             this.getBlockSize() :
488                    "Unexpected cache state.";
489 
490                previous = current;
491                length += current.length;
492                if ( !dirty )
493                {
494                    dirty = current.dirty;
495                }
496            }
497            else
498            { // Write out the current chunk and start a new one.
499                if ( dirty )
500                {
501                    this.getFileOperations().setFilePointer( startPos );
502                    this.getFileOperations().write(
503                        this.getCache(), startIndex, length );
504 
505                }
506 
507                previous = current;
508                startPos = current.block * this.getBlockSize();
509                startIndex = current.cacheIndex;
510                length = current.length;
511                dirty = current.dirty;
512            }
513        }
514 
515        if ( dirty )
516        { // Write the remaining chunk.
517            this.getFileOperations().setFilePointer( startPos );
518            this.getFileOperations().write(
519                this.getCache(), startIndex, length );
520 
521        }
522 
523        // Reset cache state.
524        for ( Iterator it = this.root.entrySet().iterator(); it.hasNext(); )
525        {
526            final Map.Entry entry = (Map.Entry) it.next();
527            final Node current = (Node) entry.getValue();
528 
529            current.cacheIndex = Node.NO_CACHEINDEX;
530            current.dirty = false;
531        }
532 
533        this.nextCacheIndex = 0;
534 
535        if ( this.getFileOperations() instanceof FlushableFileOperations )
536        { // Cache of the backing instance also needs to get flushed.
537            ( (FlushableFileOperations) this.getFileOperations() ).flush();
538        }
539    }
540 
541    //-------------------------------------------------FlushableFileOperations--
542    //--CoalescingFileOperations------------------------------------------------
543 
544    /** Node describing a cache block. */
545    private static final class Node
546    {
547 
548        private static final int NO_CACHEINDEX = Integer.MIN_VALUE;
549 
550        long block;
551 
552        int cacheIndex = NO_CACHEINDEX;
553 
554        int length;
555 
556        boolean dirty;
557 
558    }
559 
560    /** The {@code FileOperations} backing the instance. */
561    private final FileOperations fileOperations;
562 
563    /** Cached blocks. */
564    private byte[] cache;
565 
566    /** Second cache memory used during defragmentation. */
567    private byte[] defragCache;
568 
569    /** Index of the next free cached block. */
570    private int nextCacheIndex;
571 
572    /** Maps blocks to corresponding {@code Node}s. */
573    private final Map root = new TreeMap();
574 
575    /** File pointer. */
576    private long filePointer;
577 
578    /** Caches the value returned by method {@code getFilePointerBlock}. */
579    private long cachedFilePointerBlock = NO_FILEPOINTERBLOCK;
580 
581    private long cachedFilePointerBlockStart = FileOperations.EOF;
582 
583    private static final long NO_FILEPOINTERBLOCK = Long.MIN_VALUE;
584 
585    /** Flags the instance as beeing closed. */
586    private boolean closed;
587 
588    /** The number of bytes occupied by one cache block. */
589    private Integer blockSize;
590 
591    /** The number of cache blocks. */
592    private Integer cacheBlocks;
593 
594    /**
595     * Creates a new {@code CoalescingFileOperations} instance taking the
596     * {@code FileOperations} backing the instance.
597     *
598     * @param fileOperations the {@code FileOperations} backing the instance.
599     *
600     * @throws NullPointerException if {@code fileOperations} is {@code null}.
601     * @throws IOException if reading fails.
602     */
603    public CoalescingFileOperations( final FileOperations fileOperations )
604        throws IOException
605    {
606        super();
607 
608        if ( fileOperations == null )
609        {
610            throw new NullPointerException( "fileOperations" );
611        }
612 
613        this.fileOperations = fileOperations;
614        this.filePointer = fileOperations.getFilePointer();
615    }
616 
617    /**
618     * Creates a new {@code CoalescingFileOperations} instance taking the
619     * {@code FileOperations} backing the instance and the number of bytes
620     * occupied by one cache block.
621     *
622     * @param fileOperations the {@code FileOperations} backing the instance.
623     * @param blockSize the number of bytes occupied by one cache block.
624     *
625     * @throws NullPointerException if {@code fileOperations} is {@code null}.
626     * @throws IOException if reading fails.
627     */
628    public CoalescingFileOperations( final FileOperations fileOperations,
629                                     final int blockSize ) throws IOException
630    {
631        this( fileOperations );
632        if ( blockSize > 0 )
633        {
634            this.blockSize = new Integer( blockSize );
635        }
636    }
637 
638    /**
639     * Creates a new {@code CoalescingFileOperations} instance taking the
640     * {@code FileOperations} backing the instance, the number of bytes
641     * occupied by one cache block and the number of cache blocks.
642     *
643     * @param fileOperations the {@code FileOperations} backing the instance.
644     * @param blockSize the number of bytes occupied by one cache block.
645     * @param cacheBlocks number of cache blocks.
646     *
647     * @throws NullPointerException if {@code fileOperations} is {@code null}.
648     * @throws IOException if reading fails.
649     */
650    public CoalescingFileOperations( final FileOperations fileOperations,
651                                     final int blockSize,
652                                     final int cacheBlocks )
653        throws IOException
654    {
655        this( fileOperations, blockSize );
656        if ( cacheBlocks > 0 )
657        {
658            this.cacheBlocks = new Integer( cacheBlocks );
659        }
660    }
661 
662    /**
663     * Gets the {@code FileOperations} implementation operations are performed
664     * with.
665     *
666     * @return the {@code FileOperations} implementation operations are
667     * performed with.
668     */
669    public FileOperations getFileOperations()
670    {
671        return this.fileOperations;
672    }
673 
674    /**
675     * Gets the number of bytes occupied by one cache block.
676     *
677     * @return the number of bytes occupied by one cache block.
678     */
679    public int getBlockSize()
680    {
681        if ( this.blockSize == null )
682        {
683            this.blockSize = this.getDefaultBlockSize();
684        }
685 
686        return this.blockSize.intValue();
687    }
688 
689    /**
690     * Gets the number of blocks in the cache.
691     *
692     * @return the number of blocks in the cache.
693     */
694    public int getCacheBlocks()
695    {
696        if ( this.cacheBlocks == null )
697        {
698            this.cacheBlocks = this.getDefaultCacheBlocks();
699        }
700 
701        return this.cacheBlocks.intValue();
702    }
703 
704    /**
705     * Gets the cache buffer.
706     *
707     * @return the cache buffer.
708     */
709    private byte[] getCache()
710    {
711        if ( this.cache == null )
712        {
713            this.cache = this.getMemoryManager().allocateBytes(
714                this.getBlockSize() * this.getCacheBlocks() );
715 
716        }
717 
718        return this.cache;
719    }
720 
721    /**
722     * Gets the buffer used during defragmentation of the cache.
723     *
724     * @return the buffer used during defragmentation of the cache.
725     */
726    private byte[] getDefragCache()
727    {
728        if ( this.defragCache == null )
729        {
730            this.defragCache = this.getMemoryManager().allocateBytes(
731                this.getBlockSize() * this.getCacheBlocks() );
732 
733        }
734 
735        return this.defragCache;
736    }
737 
738    /**
739     * Gets the block pointed to by a given file pointer value.
740     *
741     * @param filePointer the file pointer value for which to return the
742     * corresponding block.
743     *
744     * @return the block pointed to by {@code filePointer}.
745     */
746    private long getFilePointerBlock( final long filePointer )
747    {
748        if ( this.cachedFilePointerBlock == NO_FILEPOINTERBLOCK )
749        {
750            this.cachedFilePointerBlock =
751                ( filePointer / this.getBlockSize() ) -
752                ( ( filePointer % this.getBlockSize() ) / this.getBlockSize() );
753 
754            this.cachedFilePointerBlockStart =
755                this.cachedFilePointerBlock * this.getBlockSize();
756 
757        }
758        else
759        {
760            if ( !( filePointer >= this.cachedFilePointerBlockStart &&
761                    filePointer <= this.cachedFilePointerBlockStart +
762                                   this.getBlockSize() ) )
763            {
764                this.cachedFilePointerBlock =
765                    ( filePointer / this.getBlockSize() ) -
766                    ( ( filePointer % this.getBlockSize() ) /
767                      this.getBlockSize() );
768 
769                this.cachedFilePointerBlockStart =
770                    this.cachedFilePointerBlock * this.getBlockSize();
771 
772            }
773        }
774 
775        return this.cachedFilePointerBlock;
776    }
777 
778    /**
779     * Gets the cache nodes for all blocks involved in a read or write operation
780     * of a given length for a given file pointer value.
781     *
782     * @param filePointer the file pointer value to use for computing the
783     * number of involved blocks for a read or write operation of
784     * {@code length}.
785     * @param length the length of the operation to perform.
786     *
787     * @return an array of cache nodes for all blocks involved in the
788     * operation in the order corresponding to the operation's needs.
789     */
790    private Node[] getCacheNodesForLength( final long filePointer,
791                                           final int length )
792    {
793        final long startingBlock = this.getFilePointerBlock( filePointer );
794        final long endingBlock =
795            this.getFilePointerBlock( filePointer + length );
796 
797        assert endingBlock - startingBlock <= Integer.MAX_VALUE :
798            "Unexpected implementation limit reached.";
799 
800        final Node[] nodes =
801            new Node[ (int) ( endingBlock - startingBlock + 1L ) ];
802 
803        if ( startingBlock == endingBlock )
804        {
805            nodes[0] = this.getCacheNode( startingBlock );
806        }
807        else
808        {
809            int i;
810            long block;
811 
812            for ( block = startingBlock, i = 0; block <= endingBlock;
813                  block++, i++ )
814            {
815                nodes[i] = this.getCacheNode( block );
816            }
817        }
818 
819        return nodes;
820    }
821 
822    /**
823     * Fills the cache for a given set of cache nodes.
824     * <p>This method ensures that each given node gets associated with
825     * corresponding cache memory possibly flushing the cache before
826     * reading.</p>
827     *
828     * @param nodes the nodes to fill the cache for.
829     *
830     * @throws NullPointerException if {@code nodes} is {@code null}.
831     * @throws IOException if reading fails.
832     */
833    private void fillCache( final Node[] nodes ) throws IOException
834    {
835        if ( nodes == null )
836        {
837            throw new NullPointerException( "nodes" );
838        }
839 
840        // Calculate the amount of bytes needed to be available in the cache
841        // and flush the cache if nodes would not fit.
842        long neededBytes = 0L;
843        for ( int i = nodes.length - 1; i >= 0; i-- )
844        {
845            if ( nodes[i].cacheIndex == Node.NO_CACHEINDEX )
846            { // Node's block needs to be read.
847                neededBytes += this.getBlockSize();
848            }
849        }
850 
851        if ( this.nextCacheIndex + neededBytes > this.getCache().length )
852        { // Cache cannot hold the needed blocks so needs flushing.
853            this.flush();
854        }
855 
856        // Associate each node with cache memory for nodes not already
857        // associated with cache memory and fill these nodes' cache memory.
858        for ( int i = nodes.length - 1; i >= 0; i-- )
859        {
860            if ( nodes[i].cacheIndex == Node.NO_CACHEINDEX &&
861                 this.nextCacheIndex < this.getCache().length )
862            { // Node is not associated with any cache memory and can be read.
863 
864                // Update the length field of the node for the block checking
865                // for a possible end of file condition.
866                long pos = nodes[i].block * this.getBlockSize();
867                if ( pos > this.getLength() )
868                { // Node is behind the end of the file.
869                    nodes[i].length = FileOperations.EOF;
870                    continue;
871                }
872                else if ( pos + this.getBlockSize() > this.getLength() )
873                {
874                    final long delta = this.getLength() - pos;
875 
876                    assert delta <= Integer.MAX_VALUE :
877                        "Unexpected implementation limit reached.";
878 
879                    nodes[i].length = (int) delta;
880                }
881                else
882                {
883                    nodes[i].length = this.getBlockSize();
884                }
885 
886                // Associated the node with cache memory.
887                nodes[i].cacheIndex = this.nextCacheIndex;
888                this.nextCacheIndex += this.getBlockSize();
889 
890                // Read the node's block into cache.
891                int read = FileOperations.EOF;
892                int totalRead = 0;
893                int toRead = nodes[i].length;
894                this.getFileOperations().setFilePointer( pos );
895 
896                do
897                {
898                    read = this.getFileOperations().read(
899                        this.getCache(), nodes[i].cacheIndex + totalRead,
900                        toRead );
901 
902                    assert read != FileOperations.EOF :
903                        "Unexpected end of file.";
904 
905                    totalRead += read;
906                    toRead -= read;
907 
908                }
909                while ( totalRead < nodes[i].length );
910            }
911        }
912    }
913 
914    /** Defragments the cache. */
915    private void defragmentCache()
916    {
917        int defragIndex = 0;
918 
919        // Step through the cached blocks and defragment the cache.
920        for ( Iterator it = this.root.entrySet().iterator(); it.hasNext(); )
921        {
922            final Map.Entry entry = (Map.Entry) it.next();
923            final long block = ( (Long) entry.getKey() ).longValue();
924            final Node current = (Node) entry.getValue();
925 
926            // Skip any end of file nodes and nodes not associated with memory.
927            if ( current.length == FileOperations.EOF ||
928                 current.cacheIndex == Node.NO_CACHEINDEX )
929            {
930                continue;
931            }
932 
933            assert current.block == block : "Unexpected cache state.";
934 
935            System.arraycopy( this.getCache(), current.cacheIndex,
936                              this.getDefragCache(), defragIndex,
937                              this.getBlockSize() );
938 
939            current.cacheIndex = defragIndex;
940            defragIndex += this.getBlockSize();
941        }
942 
943        System.arraycopy( this.getDefragCache(), 0, this.getCache(), 0,
944                          this.getCache().length );
945 
946    }
947 
948    /**
949     * Gets the cache node for a given block.
950     *
951     * @param block the block to return the corresponding cache node for.
952     *
953     * @return the cache node for {@code block}.
954     */
955    private Node getCacheNode( final long block )
956    {
957        final Long key = new Long( block );
958        Node node = (Node) this.root.get( key );
959        if ( node == null )
960        {
961            node = new Node();
962            node.block = block;
963            this.root.put( key, node );
964        }
965 
966        return node;
967    }
968 
969    /**
970     * Checks that the instance is not closed.
971     *
972     * @throws IOException if the instance is closed.
973     */
974    private void assertNotClosed() throws IOException
975    {
976        if ( this.closed )
977        {
978            throw new IOException( this.getAlreadyClosedMessage(
979                this.getLocale() ) );
980 
981        }
982    }
983 
984    //------------------------------------------------CoalescingFileOperations--
985    //--Messages----------------------------------------------------------------
986 
987// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausMessages
988    // This section is managed by jdtaus-container-mojo.
989 
990    /**
991     * Gets the text of message <code>readBypassesCache</code>.
992     * <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>
993     * <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>
994     *
995     * @param locale The locale of the message instance to return.
996     * @param blockSize The current block size in use.
997     * @param cacheBlocks The current number of blocks in use.
998     * @param uncachedBytes The number of bytes bypassing caching.
999     *
1000     * @return Information about a misconfigured cache.
1001     */
1002    private String getReadBypassesCacheMessage( final Locale locale,
1003            final java.lang.Number blockSize,
1004            final java.lang.Number cacheBlocks,
1005            final java.lang.Number uncachedBytes )
1006    {
1007        return ContainerFactory.getContainer().
1008            getMessage( this, "readBypassesCache", locale,
1009                new Object[]
1010                {
1011                    blockSize,
1012                    cacheBlocks,
1013                    uncachedBytes
1014                });
1015 
1016    }
1017 
1018    /**
1019     * Gets the text of message <code>writeBypassesCache</code>.
1020     * <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>
1021     * <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>
1022     *
1023     * @param locale The locale of the message instance to return.
1024     * @param blockSize The current block size in use.
1025     * @param cacheBlocks The current number of blocks in use.
1026     * @param uncachedBytes The number of bytes bypassing caching.
1027     *
1028     * @return Information about a misconfigured cache.
1029     */
1030    private String getWriteBypassesCacheMessage( final Locale locale,
1031            final java.lang.Number blockSize,
1032            final java.lang.Number cacheBlocks,
1033            final java.lang.Number uncachedBytes )
1034    {
1035        return ContainerFactory.getContainer().
1036            getMessage( this, "writeBypassesCache", locale,
1037                new Object[]
1038                {
1039                    blockSize,
1040                    cacheBlocks,
1041                    uncachedBytes
1042                });
1043 
1044    }
1045 
1046    /**
1047     * Gets the text of message <code>alreadyClosed</code>.
1048     * <blockquote><pre>Instanz geschlossen - keine E/A-Operationen möglich.</pre></blockquote>
1049     * <blockquote><pre>Instance closed - cannot perform I/O.</pre></blockquote>
1050     *
1051     * @param locale The locale of the message instance to return.
1052     *
1053     * @return Message stating that an instance is already closed.
1054     */
1055    private String getAlreadyClosedMessage( final Locale locale )
1056    {
1057        return ContainerFactory.getContainer().
1058            getMessage( this, "alreadyClosed", locale, null );
1059 
1060    }
1061 
1062// </editor-fold>//GEN-END:jdtausMessages
1063 
1064    //----------------------------------------------------------------Messages--
1065}

[all classes][org.jdtaus.core.io.util]
EMMA 2.1.5320 (stable) (C) Vladimir Roubtsov