View Javadoc

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.Locale;
27  import org.jdtaus.core.container.ContainerFactory;
28  import org.jdtaus.core.io.FileOperations;
29  import org.jdtaus.core.lang.spi.MemoryManager;
30  
31  /**
32   * Read-ahead {@code FileOperations} cache.
33   * <p>This implementation implements a read-ahead cache for
34   * {@code FileOperations} implementations. The cache is controlled by
35   * configuration property {@code cacheSize} holding the number of bytes
36   * to read-ahead. By default property {@code cacheSize} is initialized to
37   * {@code 16384} leading to a cache size of 16 kB. All memory is allocated
38   * during instantiation so that an {@code OutOfMemoryError} may be thrown
39   * when constructing the cache but not when working with the instance.</p>
40   *
41   * <p><b>Note:</b><br>
42   * This implementation is not thread-safe and concurrent changes to the
43   * underlying {@code FileOperations} implementation are not supported.</p>
44   *
45   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
46   * @version $JDTAUS: ReadAheadFileOperations.java 8641 2012-09-27 06:45:17Z schulte $
47   */
48  public final class ReadAheadFileOperations implements FlushableFileOperations
49  {
50      //--Dependencies------------------------------------------------------------
51  
52  // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies
53      // This section is managed by jdtaus-container-mojo.
54  
55      /**
56       * Gets the configured <code>MemoryManager</code> implementation.
57       *
58       * @return The configured <code>MemoryManager</code> implementation.
59       */
60      private MemoryManager getMemoryManager()
61      {
62          return (MemoryManager) ContainerFactory.getContainer().
63              getDependency( this, "MemoryManager" );
64  
65      }
66  
67      /**
68       * Gets the configured <code>Locale</code> implementation.
69       *
70       * @return The configured <code>Locale</code> implementation.
71       */
72      private Locale getLocale()
73      {
74          return (Locale) ContainerFactory.getContainer().
75              getDependency( this, "Locale" );
76  
77      }
78  
79  // </editor-fold>//GEN-END:jdtausDependencies
80  
81      //------------------------------------------------------------Dependencies--
82      //--Properties--------------------------------------------------------------
83  
84  // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausProperties
85      // This section is managed by jdtaus-container-mojo.
86  
87      /**
88       * Gets the value of property <code>defaultCacheSize</code>.
89       *
90       * @return Default cache size in byte.
91       */
92      private java.lang.Integer getDefaultCacheSize()
93      {
94          return (java.lang.Integer) ContainerFactory.getContainer().
95              getProperty( this, "defaultCacheSize" );
96  
97      }
98  
99  // </editor-fold>//GEN-END:jdtausProperties
100 
101     //--------------------------------------------------------------Properties--
102     //--FileOperations----------------------------------------------------------
103 
104     public long getLength() throws IOException
105     {
106         this.assertNotClosed();
107 
108         return this.fileOperations.getLength();
109     }
110 
111     public void setLength( final long newLength ) throws IOException
112     {
113         this.assertNotClosed();
114 
115         final long oldLength = this.getLength();
116         this.fileOperations.setLength( newLength );
117         if ( this.filePointer > newLength )
118         {
119             this.filePointer = newLength;
120         }
121 
122         if ( oldLength > newLength && this.cachePosition != NO_CACHEPOSITION &&
123              this.cachePosition + this.cacheLength >= newLength )
124         { // Discard the end of file cache.
125             this.cachePosition = NO_CACHEPOSITION;
126         }
127     }
128 
129     public long getFilePointer() throws IOException
130     {
131         this.assertNotClosed();
132 
133         return this.filePointer;
134     }
135 
136     public void setFilePointer( final long pos ) throws IOException
137     {
138         this.assertNotClosed();
139 
140         this.filePointer = pos;
141     }
142 
143     public int read( final byte[] buf, int off, int len )
144         throws IOException
145     {
146         if ( buf == null )
147         {
148             throw new NullPointerException( "buf" );
149         }
150         if ( off < 0 )
151         {
152             throw new IndexOutOfBoundsException( Integer.toString( off ) );
153         }
154         if ( len < 0 )
155         {
156             throw new IndexOutOfBoundsException( Integer.toString( len ) );
157         }
158         if ( off + len > buf.length )
159         {
160             throw new IndexOutOfBoundsException( Integer.toString( off + len ) );
161         }
162 
163         this.assertNotClosed();
164 
165         int read = FileOperations.EOF;
166 
167         final long fileLength = this.getLength();
168 
169         if ( len == 0 )
170         {
171             read = 0;
172         }
173         else if ( this.filePointer < fileLength )
174         {
175             if ( this.cachePosition == NO_CACHEPOSITION ||
176                  !( this.filePointer >= this.cachePosition &&
177                     this.filePointer < this.cachePosition + this.cacheLength ) )
178             { // Cache not initialized or file pointer outside the cached area.
179                 this.fillCache();
180             }
181 
182             final long cacheStart = this.filePointer - this.cachePosition;
183 
184             assert cacheStart <= Integer.MAX_VALUE :
185                 "Unexpected implementation limit reached.";
186 
187             final int cachedLength = len > this.cacheLength -
188                                            (int) cacheStart
189                                      ? this.cacheLength - (int) cacheStart
190                                      : len;
191 
192             System.arraycopy( this.getCache(), (int) cacheStart, buf, off,
193                               cachedLength );
194 
195             len -= cachedLength;
196             off += cachedLength;
197             read = cachedLength;
198             this.filePointer += cachedLength;
199         }
200 
201         return read;
202     }
203 
204     public void write( final byte[] buf, final int off, final int len )
205         throws IOException
206     {
207         if ( buf == null )
208         {
209             throw new NullPointerException( "buf" );
210         }
211         if ( off < 0 )
212         {
213             throw new IndexOutOfBoundsException( Integer.toString( off ) );
214         }
215         if ( len < 0 )
216         {
217             throw new IndexOutOfBoundsException( Integer.toString( len ) );
218         }
219         if ( off + len > buf.length )
220         {
221             throw new IndexOutOfBoundsException( Integer.toString( off + len ) );
222         }
223 
224         this.assertNotClosed();
225 
226         if ( this.cachePosition != NO_CACHEPOSITION &&
227              this.filePointer >= this.cachePosition &&
228              this.filePointer < this.cachePosition + this.cacheLength )
229         { // Cache needs updating.
230             final long cacheStart = this.filePointer - this.cachePosition;
231 
232             assert cacheStart <= Integer.MAX_VALUE :
233                 "Unexpected implementation limit reached.";
234 
235             final int cachedLength = len > this.cacheLength -
236                                            (int) cacheStart
237                                      ? this.cacheLength - (int) cacheStart
238                                      : len;
239 
240             System.arraycopy( buf, off, this.getCache(), (int) cacheStart,
241                               cachedLength );
242 
243         }
244 
245         this.fileOperations.setFilePointer( this.filePointer );
246         this.fileOperations.write( buf, off, len );
247         this.filePointer += len;
248     }
249 
250     public void read( final OutputStream out ) throws IOException
251     {
252         this.assertNotClosed();
253 
254         this.fileOperations.read( out );
255         this.filePointer = this.fileOperations.getFilePointer();
256     }
257 
258     public void write( final InputStream in ) throws IOException
259     {
260         this.assertNotClosed();
261 
262         this.fileOperations.write( in );
263         this.filePointer = this.fileOperations.getFilePointer();
264     }
265 
266     /**
267      * {@inheritDoc}
268      * Flushes the cache and closes the {@code FileOperations} implementation
269      * backing the instance.
270      *
271      * @throws IOException if closing the {@code FileOperations} implementation
272      * backing the instance fails or if the instance already is closed.
273      */
274     public void close() throws IOException
275     {
276         this.assertNotClosed();
277 
278         this.flush();
279         this.getFileOperations().close();
280         this.closed = true;
281     }
282 
283     //----------------------------------------------------------FileOperations--
284     //--FlushableFileOperations-------------------------------------------------
285 
286     /**
287      * {@inheritDoc}
288      * This method calls the {@code flush()} method of an underlying
289      * {@code FlushableFileOperations} implementation, if any.
290      */
291     public void flush() throws IOException
292     {
293         this.assertNotClosed();
294 
295         if ( this.fileOperations instanceof FlushableFileOperations )
296         {
297             ( (FlushableFileOperations) this.fileOperations ).flush();
298         }
299     }
300 
301     //-------------------------------------------------FlushableFileOperations--
302     //--ReadAheadFileOperations-------------------------------------------------
303 
304     /** The {@code FileOperations} backing the instance. */
305     private final FileOperations fileOperations;
306 
307     /** Cached bytes. */
308     private byte[] cache;
309 
310     /** Position in the file {@code cache} starts. */
311     private long cachePosition;
312 
313     private static final long NO_CACHEPOSITION = Long.MIN_VALUE;
314 
315     /** Length of the cached data. */
316     private int cacheLength;
317 
318     /** File pointer value. */
319     private long filePointer;
320 
321     /** Flags the instance as beeing closed. */
322     private boolean closed;
323 
324     /** Cache size in byte. */
325     private Integer cacheSize;
326 
327     /**
328      * Creates a new {@code ReadAheadFileOperations} instance taking the
329      * {@code FileOperations} backing the instance.
330      *
331      * @param fileOperations the {@code FileOperations} backing the instance.
332      *
333      * @throws NullPointerException if {@code fileOperations} is {@code null}.
334      * @throws IOException if reading fails.
335      */
336     public ReadAheadFileOperations( final FileOperations fileOperations )
337         throws IOException
338     {
339         super();
340 
341         if ( fileOperations == null )
342         {
343             throw new NullPointerException( "fileOperations" );
344         }
345 
346         this.fileOperations = fileOperations;
347         this.filePointer = fileOperations.getFilePointer();
348     }
349 
350     /**
351      * Creates a new {@code ReadAheadFileOperations} instance taking the
352      * {@code FileOperations} backing the instance and the size of the cache.
353      *
354      * @param fileOperations the {@code FileOperations} backing the instance.
355      * @param cacheSize the number of bytes to read-ahead.
356      *
357      * @throws NullPointerException if {@code fileOperations} is {@code null}.
358      * @throws IOException if reading fails.
359      */
360     public ReadAheadFileOperations( final FileOperations fileOperations,
361                                     final int cacheSize ) throws IOException
362     {
363         this( fileOperations );
364 
365         if ( cacheSize > 0 )
366         {
367             this.cacheSize = new Integer( cacheSize );
368         }
369     }
370 
371     /**
372      * Gets the {@code FileOperations} implementation operations are performed
373      * with.
374      *
375      * @return the {@code FileOperations} implementation operations are
376      * performed with.
377      */
378     public FileOperations getFileOperations()
379     {
380         return this.fileOperations;
381     }
382 
383     /**
384      * Gets the size of the cache in byte.
385      *
386      * @return the size of the cache in byte.
387      */
388     public int getCacheSize()
389     {
390         if ( this.cacheSize == null )
391         {
392             this.cacheSize = this.getDefaultCacheSize();
393         }
394 
395         return this.cacheSize.intValue();
396     }
397 
398     /**
399      * Gets the cache buffer.
400      *
401      * @return the cache buffer.
402      */
403     private byte[] getCache()
404     {
405         if ( this.cache == null )
406         {
407             this.cache =
408                 this.getMemoryManager().allocateBytes( this.getCacheSize() );
409 
410         }
411 
412         return this.cache;
413     }
414 
415     /**
416      * Checks that the instance is not closed.
417      *
418      * @throws IOException if the instance is closed.
419      */
420     private void assertNotClosed() throws IOException
421     {
422         if ( this.closed )
423         {
424             throw new IOException( this.getAlreadyClosedMessage(
425                 this.getLocale() ) );
426 
427         }
428     }
429 
430     /**
431      * Fills the cache starting at the current file pointer value.
432      *
433      * @throws IOException if reading fails.
434      */
435     private void fillCache() throws IOException
436     {
437         final long delta = this.getLength() - this.filePointer;
438         final int toRead = delta > this.getCache().length
439                            ? this.getCache().length
440                            : (int) delta;
441 
442         this.cachePosition = this.filePointer;
443 
444         int totalRead = 0;
445         int readLength = toRead;
446 
447         do
448         {
449             this.fileOperations.setFilePointer( this.filePointer );
450             final int read = this.fileOperations.read(
451                 this.getCache(), totalRead, readLength );
452 
453             assert read != FileOperations.EOF : "Unexpected end of file.";
454 
455             totalRead += read;
456             readLength -= read;
457 
458         }
459         while ( totalRead < toRead );
460 
461         this.cacheLength = toRead;
462     }
463 
464     //-------------------------------------------------ReadAheadFileOperations--
465     //--Messages----------------------------------------------------------------
466 
467 // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausMessages
468     // This section is managed by jdtaus-container-mojo.
469 
470     /**
471      * Gets the text of message <code>alreadyClosed</code>.
472      * <blockquote><pre>Instanz geschlossen - keine E/A-Operationen möglich.</pre></blockquote>
473      * <blockquote><pre>Instance closed - cannot perform I/O.</pre></blockquote>
474      *
475      * @param locale The locale of the message instance to return.
476      *
477      * @return Message stating that an instance is already closed.
478      */
479     private String getAlreadyClosedMessage( final Locale locale )
480     {
481         return ContainerFactory.getContainer().
482             getMessage( this, "alreadyClosed", locale, null );
483 
484     }
485 
486 // </editor-fold>//GEN-END:jdtausMessages
487 
488     //----------------------------------------------------------------Messages--
489 }