EMMA Coverage Report (generated Tue Jan 14 02:29:45 CET 2014)
[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)88%  (1315/1502)89%  (252.7/284)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class CoalescingFileOperations100% (1/1)88%  (29/33)88%  (1309/1496)89%  (249.7/281)
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%  (177/212)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%  (158/181)95%  (25.6/27)
fillCache (CoalescingFileOperations$Node []): void 100% (1/1)92%  (175/190)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%  (181/195)96%  (48.2/50)
defragmentCache (): void 100% (1/1)93%  (70/75)97%  (14.6/15)
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% (30/30)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% (3/3)
CoalescingFileOperations$Node (): void 100% (1/1)100% (6/6)100% (3/3)

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 8743 2012-10-07 03:06:20Z 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 ( 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}

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