001/* 002 * jDTAUS Core Utilities 003 * Copyright (C) 2005 Christian Schulte 004 * <cs@schulte.it> 005 * 006 * This library is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 2.1 of the License, or any later version. 010 * 011 * This library is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with this library; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 019 * 020 */ 021package org.jdtaus.core.io.util; 022 023import java.io.IOException; 024import java.io.InputStream; 025import java.io.OutputStream; 026import java.util.Locale; 027import org.jdtaus.core.container.ContainerFactory; 028import org.jdtaus.core.io.FileOperations; 029import org.jdtaus.core.lang.spi.MemoryManager; 030 031/** 032 * Read-ahead {@code FileOperations} cache. 033 * <p>This implementation implements a read-ahead cache for 034 * {@code FileOperations} implementations. The cache is controlled by 035 * configuration property {@code cacheSize} holding the number of bytes 036 * to read-ahead. By default property {@code cacheSize} is initialized to 037 * {@code 16384} leading to a cache size of 16 kB. All memory is allocated 038 * during instantiation so that an {@code OutOfMemoryError} may be thrown 039 * when constructing the cache but not when working with the instance.</p> 040 * 041 * <p><b>Note:</b><br> 042 * This implementation is not thread-safe and concurrent changes to the 043 * underlying {@code FileOperations} implementation are not supported.</p> 044 * 045 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> 046 * @version $JDTAUS: ReadAheadFileOperations.java 8641 2012-09-27 06:45:17Z schulte $ 047 */ 048public final class ReadAheadFileOperations implements FlushableFileOperations 049{ 050 //--Dependencies------------------------------------------------------------ 051 052// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies 053 // This section is managed by jdtaus-container-mojo. 054 055 /** 056 * Gets the configured <code>MemoryManager</code> implementation. 057 * 058 * @return The configured <code>MemoryManager</code> implementation. 059 */ 060 private MemoryManager getMemoryManager() 061 { 062 return (MemoryManager) ContainerFactory.getContainer(). 063 getDependency( this, "MemoryManager" ); 064 065 } 066 067 /** 068 * Gets the configured <code>Locale</code> implementation. 069 * 070 * @return The configured <code>Locale</code> implementation. 071 */ 072 private Locale getLocale() 073 { 074 return (Locale) ContainerFactory.getContainer(). 075 getDependency( this, "Locale" ); 076 077 } 078 079// </editor-fold>//GEN-END:jdtausDependencies 080 081 //------------------------------------------------------------Dependencies-- 082 //--Properties-------------------------------------------------------------- 083 084// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausProperties 085 // This section is managed by jdtaus-container-mojo. 086 087 /** 088 * Gets the value of property <code>defaultCacheSize</code>. 089 * 090 * @return Default cache size in byte. 091 */ 092 private java.lang.Integer getDefaultCacheSize() 093 { 094 return (java.lang.Integer) ContainerFactory.getContainer(). 095 getProperty( this, "defaultCacheSize" ); 096 097 } 098 099// </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}