| 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.math.BigDecimal; |
| 25 | import java.util.Locale; |
| 26 | import javax.swing.event.EventListenerList; |
| 27 | import org.jdtaus.core.container.ContainerFactory; |
| 28 | import org.jdtaus.core.io.FileOperations; |
| 29 | import org.jdtaus.core.io.StructuredFile; |
| 30 | import org.jdtaus.core.io.StructuredFileListener; |
| 31 | import org.jdtaus.core.lang.spi.MemoryManager; |
| 32 | import org.jdtaus.core.messages.DeletesBlocksMessage; |
| 33 | import org.jdtaus.core.messages.InsertsBlocksMessage; |
| 34 | import org.jdtaus.core.monitor.spi.Task; |
| 35 | import org.jdtaus.core.monitor.spi.TaskMonitor; |
| 36 | |
| 37 | /** |
| 38 | * {@code StructuredFile} implementation based on {@code FileOperations}. |
| 39 | * <p>Pre {@code FlushableFileOperations} and its implementations this |
| 40 | * implementation performed read-ahead caching. This behaviour got changed |
| 41 | * in favour of {@code ReadAheadFileOperations} and |
| 42 | * {@code CoalescingFileOperations} which are generalized replacements for any |
| 43 | * cacheing formerly performed by this implementation. Since this class does |
| 44 | * not implement any cacheing anymore, the {@link #flush()} method will write |
| 45 | * out pending changes of an underlying {@code FlushableFileOperations} |
| 46 | * implementation, if any, by calling the corresponding {@code flush()} method |
| 47 | * of that {@code FlushableFileOperations} instance.</p> |
| 48 | * <p>This implementation uses task monitoring for the {@code deleteBlocks()} |
| 49 | * and {@code insertBlocks()} methods. Task monitoring is controlled by |
| 50 | * property {@code monitoringThreshold} holding the number of bytes which |
| 51 | * need to minimally be copied to enable any task monitoring during the |
| 52 | * copy operation (defaults to 5242880 - 5MB).</p> |
| 53 | * |
| 54 | * <p><b>Note:</b><br> |
| 55 | * This implementation is not thread-safe and concurrent changes to the |
| 56 | * underlying {@code FileOperations} implementation are not supported.</p> |
| 57 | * |
| 58 | * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> |
| 59 | * @version $JDTAUS: StructuredFileOperations.java 8641 2012-09-27 06:45:17Z schulte $ |
| 60 | * |
| 61 | * @see CoalescingFileOperations |
| 62 | * @see ReadAheadFileOperations |
| 63 | */ |
| 64 | public final class StructuredFileOperations implements StructuredFile |
| 65 | { |
| 66 | //--Fields------------------------------------------------------------------ |
| 67 | |
| 68 | /** Pre-allocated temporary buffer. */ |
| 69 | private byte[] defaultBuffer; |
| 70 | |
| 71 | /** Caches the value of property blockCount. */ |
| 72 | private long cachedBlockCount = NO_CACHED_BLOCKCOUNT; |
| 73 | |
| 74 | private static final long NO_CACHED_BLOCKCOUNT = Long.MIN_VALUE; |
| 75 | |
| 76 | /** List for {@code StructuredFileListener}s. */ |
| 77 | private final EventListenerList fileListeners = new EventListenerList(); |
| 78 | |
| 79 | /** Value of property {@code blockSize} as a {@code BigDecimal}. */ |
| 80 | private final BigDecimal decimalBlockSize; |
| 81 | |
| 82 | //------------------------------------------------------------------Fields-- |
| 83 | //--Dependencies------------------------------------------------------------ |
| 84 | |
| 85 | // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies |
| 86 | // This section is managed by jdtaus-container-mojo. |
| 87 | |
| 88 | /** |
| 89 | * Gets the configured <code>MemoryManager</code> implementation. |
| 90 | * |
| 91 | * @return The configured <code>MemoryManager</code> implementation. |
| 92 | */ |
| 93 | private MemoryManager getMemoryManager() |
| 94 | { |
| 95 | return (MemoryManager) ContainerFactory.getContainer(). |
| 96 | getDependency( this, "MemoryManager" ); |
| 97 | |
| 98 | } |
| 99 | |
| 100 | /** |
| 101 | * Gets the configured <code>Locale</code> implementation. |
| 102 | * |
| 103 | * @return The configured <code>Locale</code> implementation. |
| 104 | */ |
| 105 | private Locale getLocale() |
| 106 | { |
| 107 | return (Locale) ContainerFactory.getContainer(). |
| 108 | getDependency( this, "Locale" ); |
| 109 | |
| 110 | } |
| 111 | |
| 112 | /** |
| 113 | * Gets the configured <code>TaskMonitor</code> implementation. |
| 114 | * |
| 115 | * @return The configured <code>TaskMonitor</code> implementation. |
| 116 | */ |
| 117 | private TaskMonitor getTaskMonitor() |
| 118 | { |
| 119 | return (TaskMonitor) ContainerFactory.getContainer(). |
| 120 | getDependency( this, "TaskMonitor" ); |
| 121 | |
| 122 | } |
| 123 | |
| 124 | // </editor-fold>//GEN-END:jdtausDependencies |
| 125 | |
| 126 | //------------------------------------------------------------Dependencies-- |
| 127 | //--Properties-------------------------------------------------------------- |
| 128 | |
| 129 | // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausProperties |
| 130 | // This section is managed by jdtaus-container-mojo. |
| 131 | |
| 132 | /** |
| 133 | * Gets the value of property <code>defaultMonitoringThreshold</code>. |
| 134 | * |
| 135 | * @return Number of bytes which need to minimally be copied to enable any task monitoring during copy operations. |
| 136 | */ |
| 137 | private java.lang.Integer getDefaultMonitoringThreshold() |
| 138 | { |
| 139 | return (java.lang.Integer) ContainerFactory.getContainer(). |
| 140 | getProperty( this, "defaultMonitoringThreshold" ); |
| 141 | |
| 142 | } |
| 143 | |
| 144 | /** |
| 145 | * Gets the value of property <code>defaultBufferSize</code>. |
| 146 | * |
| 147 | * @return Size of the pre-alocated default buffer in byte. |
| 148 | */ |
| 149 | private int getDefaultBufferSize() |
| 150 | { |
| 151 | return ( (java.lang.Integer) ContainerFactory.getContainer(). |
| 152 | getProperty( this, "defaultBufferSize" ) ).intValue(); |
| 153 | |
| 154 | } |
| 155 | |
| 156 | // </editor-fold>//GEN-END:jdtausProperties |
| 157 | |
| 158 | //--------------------------------------------------------------Properties-- |
| 159 | //--StructuredFile---------------------------------------------------------- |
| 160 | |
| 161 | public int getBlockSize() |
| 162 | { |
| 163 | return this.blockSize; |
| 164 | } |
| 165 | |
| 166 | public long getBlockCount() throws IOException |
| 167 | { |
| 168 | this.assertNotClosed(); |
| 169 | |
| 170 | if ( this.cachedBlockCount == NO_CACHED_BLOCKCOUNT ) |
| 171 | { |
| 172 | this.cachedBlockCount = BigDecimal.valueOf( |
| 173 | this.getFileOperations().getLength() ).divide( |
| 174 | this.decimalBlockSize, BigDecimal.ROUND_UNNECESSARY ). |
| 175 | longValue(); |
| 176 | |
| 177 | // TODO JDK 1.5 longValueExact() |
| 178 | } |
| 179 | |
| 180 | return this.cachedBlockCount; |
| 181 | } |
| 182 | |
| 183 | public void deleteBlocks( final long index, |
| 184 | final long count ) throws IOException |
| 185 | { |
| 186 | final long blockCount = this.getBlockCount(); |
| 187 | |
| 188 | // Preconditions. |
| 189 | if ( index < 0L || index > blockCount - count ) |
| 190 | { |
| 191 | throw new ArrayIndexOutOfBoundsException( (int) index ); |
| 192 | } |
| 193 | if ( count <= 0 || count > blockCount - index ) |
| 194 | { |
| 195 | throw new ArrayIndexOutOfBoundsException( (int) count ); |
| 196 | } |
| 197 | |
| 198 | this.assertNotClosed(); |
| 199 | |
| 200 | this.deleteBlocksImpl( index, count, blockCount ); |
| 201 | } |
| 202 | |
| 203 | private void deleteBlocksImpl( final long index, final long count, |
| 204 | final long blockCount ) throws IOException |
| 205 | { |
| 206 | final long block = index + count; |
| 207 | final Task task = new Task(); |
| 208 | long toMoveByte = ( blockCount - block ) * this.getBlockSize(); |
| 209 | long readPos = block * this.getBlockSize(); |
| 210 | long writePos = index * this.getBlockSize(); |
| 211 | long progress = 0L; |
| 212 | long progressDivisor = 1L; |
| 213 | long maxProgress = toMoveByte; |
| 214 | |
| 215 | // Clear the cached block count. |
| 216 | this.cachedBlockCount = NO_CACHED_BLOCKCOUNT; |
| 217 | |
| 218 | // No blocks are following the ones to remove. |
| 219 | if ( toMoveByte == 0L ) |
| 220 | { |
| 221 | this.getFileOperations().setLength( |
| 222 | this.getFileOperations().getLength() - count * |
| 223 | this.getBlockSize() ); |
| 224 | |
| 225 | this.fireBlocksDeleted( index, count ); |
| 226 | return; |
| 227 | } |
| 228 | |
| 229 | final byte[] buf = this.getBuffer( toMoveByte > Integer.MAX_VALUE |
| 230 | ? Integer.MAX_VALUE |
| 231 | : (int) toMoveByte ); |
| 232 | |
| 233 | while ( maxProgress > Integer.MAX_VALUE ) |
| 234 | { |
| 235 | maxProgress /= 2L; |
| 236 | progressDivisor *= 2L; |
| 237 | } |
| 238 | |
| 239 | task.setIndeterminate( false ); |
| 240 | task.setCancelable( false ); |
| 241 | task.setMinimum( 0 ); |
| 242 | task.setMaximum( (int) maxProgress ); |
| 243 | task.setProgress( (int) progress ); |
| 244 | task.setDescription( new DeletesBlocksMessage() ); |
| 245 | |
| 246 | final boolean monitoring = toMoveByte > this.getMonitoringThreshold(); |
| 247 | if ( monitoring ) |
| 248 | { |
| 249 | this.getTaskMonitor().monitor( task ); |
| 250 | } |
| 251 | |
| 252 | try |
| 253 | { |
| 254 | // Move following blocks to the position of the first block to |
| 255 | // remove. |
| 256 | while ( toMoveByte > 0L ) |
| 257 | { |
| 258 | this.getFileOperations().setFilePointer( readPos ); |
| 259 | final int len = toMoveByte <= buf.length |
| 260 | ? (int) toMoveByte |
| 261 | : buf.length; |
| 262 | |
| 263 | int read = 0; |
| 264 | int total = 0; |
| 265 | do |
| 266 | { |
| 267 | read = this.getFileOperations(). |
| 268 | read( buf, total, len - total ); |
| 269 | |
| 270 | assert read != FileOperations.EOF : |
| 271 | "Unexpected end of file."; |
| 272 | |
| 273 | total += read; |
| 274 | |
| 275 | } |
| 276 | while ( total < len ); |
| 277 | |
| 278 | // Move the block count blocks to the beginning. |
| 279 | this.getFileOperations().setFilePointer( writePos ); |
| 280 | this.getFileOperations().write( buf, 0, len ); |
| 281 | |
| 282 | readPos += len; |
| 283 | writePos += len; |
| 284 | toMoveByte -= len; |
| 285 | progress += len; |
| 286 | |
| 287 | task.setProgress( (int) ( progress / progressDivisor ) ); |
| 288 | } |
| 289 | |
| 290 | // Truncate the file. |
| 291 | this.getFileOperations().setLength( this.getFileOperations(). |
| 292 | getLength() - count * |
| 293 | this.getBlockSize() ); |
| 294 | |
| 295 | this.fireBlocksDeleted( index, count ); |
| 296 | } |
| 297 | finally |
| 298 | { |
| 299 | if ( monitoring ) |
| 300 | { |
| 301 | this.getTaskMonitor().finish( task ); |
| 302 | } |
| 303 | } |
| 304 | } |
| 305 | |
| 306 | public void insertBlocks( final long index, |
| 307 | final long count ) throws IOException |
| 308 | { |
| 309 | final long blockCount = this.getBlockCount(); |
| 310 | |
| 311 | // Preconditions. |
| 312 | if ( index < 0L || index > blockCount ) |
| 313 | { |
| 314 | throw new ArrayIndexOutOfBoundsException( (int) index ); |
| 315 | } |
| 316 | if ( count <= 0L || count > Long.MAX_VALUE - blockCount ) |
| 317 | { |
| 318 | throw new ArrayIndexOutOfBoundsException( (int) count ); |
| 319 | } |
| 320 | |
| 321 | this.assertNotClosed(); |
| 322 | |
| 323 | this.insertBlocksImpl( index, count, blockCount ); |
| 324 | } |
| 325 | |
| 326 | private void insertBlocksImpl( final long index, final long count, |
| 327 | final long blockCount ) throws IOException |
| 328 | { |
| 329 | final Task task = new Task(); |
| 330 | long toMoveByte = ( blockCount - index ) * this.getBlockSize(); |
| 331 | long readPos = blockCount * this.getBlockSize(); |
| 332 | long writePos = readPos + count * this.getBlockSize(); |
| 333 | long progress = 0L; |
| 334 | long progressDivisor = 1L; |
| 335 | long maxProgress = toMoveByte; |
| 336 | |
| 337 | // Clear the cached block count. |
| 338 | this.cachedBlockCount = NO_CACHED_BLOCKCOUNT; |
| 339 | |
| 340 | // Increase the length of the file. |
| 341 | this.getFileOperations().setLength( this.getFileOperations(). |
| 342 | getLength() + this.getBlockSize() * |
| 343 | count ); |
| 344 | |
| 345 | // New blocks are inserted at the end of the file. |
| 346 | if ( toMoveByte <= 0L ) |
| 347 | { |
| 348 | this.fireBlocksInserted( index, count ); |
| 349 | return; |
| 350 | } |
| 351 | |
| 352 | final byte[] buf = this.getBuffer( toMoveByte > Integer.MAX_VALUE |
| 353 | ? Integer.MAX_VALUE |
| 354 | : (int) toMoveByte ); |
| 355 | |
| 356 | while ( maxProgress > Integer.MAX_VALUE ) |
| 357 | { |
| 358 | maxProgress /= 2L; |
| 359 | progressDivisor *= 2L; |
| 360 | } |
| 361 | |
| 362 | task.setIndeterminate( false ); |
| 363 | task.setCancelable( false ); |
| 364 | task.setMinimum( 0 ); |
| 365 | task.setMaximum( (int) maxProgress ); |
| 366 | task.setProgress( (int) progress ); |
| 367 | task.setDescription( new InsertsBlocksMessage() ); |
| 368 | |
| 369 | final boolean monitoring = toMoveByte > this.getMonitoringThreshold(); |
| 370 | if ( monitoring ) |
| 371 | { |
| 372 | this.getTaskMonitor().monitor( task ); |
| 373 | } |
| 374 | |
| 375 | try |
| 376 | { |
| 377 | // Move all blocks from index inclusive count blocks to the end of |
| 378 | // the file. |
| 379 | while ( toMoveByte > 0L ) |
| 380 | { |
| 381 | final int moveLen = buf.length >= toMoveByte |
| 382 | ? (int) toMoveByte |
| 383 | : buf.length; |
| 384 | |
| 385 | readPos -= moveLen; |
| 386 | writePos -= moveLen; |
| 387 | this.getFileOperations().setFilePointer( readPos ); |
| 388 | int read = 0; |
| 389 | int total = 0; |
| 390 | |
| 391 | do |
| 392 | { |
| 393 | read = this.getFileOperations(). |
| 394 | read( buf, total, moveLen - total ); |
| 395 | |
| 396 | assert read != FileOperations.EOF : |
| 397 | "Unexpected end of file."; |
| 398 | |
| 399 | total += read; |
| 400 | |
| 401 | } |
| 402 | while ( total < moveLen ); |
| 403 | |
| 404 | // Move the block count blocks to the end. |
| 405 | this.getFileOperations().setFilePointer( writePos ); |
| 406 | this.getFileOperations().write( buf, 0, moveLen ); |
| 407 | |
| 408 | toMoveByte -= moveLen; |
| 409 | progress += moveLen; |
| 410 | |
| 411 | task.setProgress( (int) ( progress / progressDivisor ) ); |
| 412 | } |
| 413 | |
| 414 | this.fireBlocksInserted( index, count ); |
| 415 | } |
| 416 | finally |
| 417 | { |
| 418 | if ( monitoring ) |
| 419 | { |
| 420 | this.getTaskMonitor().finish( task ); |
| 421 | } |
| 422 | } |
| 423 | } |
| 424 | |
| 425 | public void readBlock( final long block, final int off, |
| 426 | final byte[] buf ) throws IOException |
| 427 | { |
| 428 | this.readBlock( block, off, buf, 0, buf.length ); |
| 429 | } |
| 430 | |
| 431 | public void readBlock( final long block, final int off, final byte[] buf, |
| 432 | final int index, final int length ) |
| 433 | throws IOException |
| 434 | { |
| 435 | this.assertValidArguments( block, off, buf, index, length ); |
| 436 | this.assertNotClosed(); |
| 437 | |
| 438 | int totalRead = 0; |
| 439 | int toRead = length; |
| 440 | |
| 441 | this.getFileOperations().setFilePointer( |
| 442 | block * this.getBlockSize() + off ); |
| 443 | |
| 444 | do |
| 445 | { |
| 446 | final int read = this.getFileOperations(). |
| 447 | read( buf, index + totalRead, toRead ); |
| 448 | |
| 449 | assert read != FileOperations.EOF : |
| 450 | "Unexpected end of file."; |
| 451 | |
| 452 | totalRead += read; |
| 453 | toRead -= read; |
| 454 | |
| 455 | } |
| 456 | while ( totalRead < length ); |
| 457 | } |
| 458 | |
| 459 | public void writeBlock( final long block, final int off, |
| 460 | final byte[] buf ) throws IOException |
| 461 | { |
| 462 | this.writeBlock( block, off, buf, 0, buf.length ); |
| 463 | } |
| 464 | |
| 465 | public void writeBlock( final long block, final int off, |
| 466 | final byte[] buf, |
| 467 | final int index, final int length ) |
| 468 | throws IOException |
| 469 | { |
| 470 | this.assertValidArguments( block, off, buf, index, length ); |
| 471 | this.assertNotClosed(); |
| 472 | |
| 473 | this.getFileOperations().setFilePointer( |
| 474 | block * this.getBlockSize() + off ); |
| 475 | |
| 476 | this.getFileOperations().write( buf, index, length ); |
| 477 | } |
| 478 | |
| 479 | /** |
| 480 | * {@inheritDoc} |
| 481 | * Flushes the instance and closes the {@code FileOperations} implementation |
| 482 | * backing the instance. |
| 483 | * |
| 484 | * @throws IOException if closing the {@code FileOperations} implementation |
| 485 | * backing the instance fails, or if the instance already is closed. |
| 486 | */ |
| 487 | public void close() throws IOException |
| 488 | { |
| 489 | this.assertNotClosed(); |
| 490 | |
| 491 | this.flush(); |
| 492 | this.getFileOperations().close(); |
| 493 | this.closed = true; |
| 494 | } |
| 495 | |
| 496 | public void addStructuredFileListener( |
| 497 | final StructuredFileListener listener ) |
| 498 | { |
| 499 | this.fileListeners.add( StructuredFileListener.class, listener ); |
| 500 | } |
| 501 | |
| 502 | public void removeStructuredFileListener( |
| 503 | final StructuredFileListener listener ) |
| 504 | { |
| 505 | this.fileListeners.remove( StructuredFileListener.class, listener ); |
| 506 | } |
| 507 | |
| 508 | public StructuredFileListener[] getStructuredFileListeners() |
| 509 | { |
| 510 | return (StructuredFileListener[]) this.fileListeners.getListeners( |
| 511 | StructuredFileListener.class ); |
| 512 | |
| 513 | } |
| 514 | |
| 515 | //----------------------------------------------------------StructuredFile-- |
| 516 | //--StructuredFileOperations------------------------------------------------ |
| 517 | |
| 518 | /** Number of bytes per block. */ |
| 519 | private int blockSize; |
| 520 | |
| 521 | /** Mininum number of bytes to copy to start any task monitoring. */ |
| 522 | private Integer monitoringThreshold; |
| 523 | |
| 524 | /** {@code FileOperations} backing the instance. */ |
| 525 | private FileOperations fileOperations; |
| 526 | |
| 527 | /** Flags the instance as beeing closed. */ |
| 528 | private boolean closed; |
| 529 | |
| 530 | /** |
| 531 | * Creates a new {@code StructuredFileOperations} instance taking the size |
| 532 | * of one block in byte and the {@code FileOperations} operations are to be |
| 533 | * performed with. |
| 534 | * |
| 535 | * @param blockSize Number of bytes per block. |
| 536 | * @param fileOperations {@code FileOperations} implementation to operate |
| 537 | * on. |
| 538 | * |
| 539 | * @throws NullPointerException if {@code fileOperations} is {@code null}. |
| 540 | * @throws IllegalArgumentException if {@code blockSize} is incompatible |
| 541 | * with the length of {@code fileOperations}. |
| 542 | * @throws IOException if getting the length from the {@code fileOperations} |
| 543 | * fails. |
| 544 | */ |
| 545 | public StructuredFileOperations( final int blockSize, |
| 546 | final FileOperations fileOperations ) |
| 547 | throws IOException |
| 548 | { |
| 549 | super(); |
| 550 | |
| 551 | if ( fileOperations == null ) |
| 552 | { |
| 553 | throw new NullPointerException( "fileOperations" ); |
| 554 | } |
| 555 | if ( blockSize <= 0 ) |
| 556 | { |
| 557 | throw new IllegalArgumentException( Integer.toString( blockSize ) ); |
| 558 | } |
| 559 | |
| 560 | this.blockSize = blockSize; |
| 561 | this.decimalBlockSize = BigDecimal.valueOf( blockSize ); |
| 562 | this.fileOperations = fileOperations; |
| 563 | this.assertValidFileLength(); |
| 564 | } |
| 565 | |
| 566 | /** |
| 567 | * Creates a new {@code StructuredFileOperations} instance taking the size |
| 568 | * of one block in byte, task monitoring configuration and the |
| 569 | * {@code FileOperations} operations are to be performed with. |
| 570 | * |
| 571 | * @param blockSize Number of bytes per block. |
| 572 | * @param monitoringThreshold the mininum number of bytes to copy to start |
| 573 | * any task monitoring. |
| 574 | * @param fileOperations {@code FileOperations} implementation to operate |
| 575 | * on. |
| 576 | * |
| 577 | * @throws NullPointerException if {@code fileOperations} is {@code null}. |
| 578 | * @throws IllegalArgumentException if {@code blockSize} is incompatible |
| 579 | * with the length of {@code fileOperations}. |
| 580 | * @throws IOException if getting the length from the {@code fileOperations} |
| 581 | * fails. |
| 582 | */ |
| 583 | public StructuredFileOperations( final int blockSize, |
| 584 | final int monitoringThreshold, |
| 585 | final FileOperations fileOperations ) |
| 586 | throws IOException |
| 587 | { |
| 588 | super(); |
| 589 | |
| 590 | if ( fileOperations == null ) |
| 591 | { |
| 592 | throw new NullPointerException( "fileOperations" ); |
| 593 | } |
| 594 | if ( blockSize <= 0 ) |
| 595 | { |
| 596 | throw new IllegalArgumentException( Integer.toString( blockSize ) ); |
| 597 | } |
| 598 | |
| 599 | this.blockSize = blockSize; |
| 600 | this.decimalBlockSize = BigDecimal.valueOf( blockSize ); |
| 601 | this.fileOperations = fileOperations; |
| 602 | |
| 603 | if ( monitoringThreshold > 0 ) |
| 604 | { |
| 605 | this.monitoringThreshold = new Integer( monitoringThreshold ); |
| 606 | } |
| 607 | |
| 608 | this.assertValidFileLength(); |
| 609 | } |
| 610 | |
| 611 | /** |
| 612 | * Gets the {@code FileOperations} implementation operations are performed |
| 613 | * with. |
| 614 | * |
| 615 | * @return the {@code FileOperations} implementation operations are |
| 616 | * performed with. |
| 617 | */ |
| 618 | public FileOperations getFileOperations() |
| 619 | { |
| 620 | return this.fileOperations; |
| 621 | } |
| 622 | |
| 623 | /** |
| 624 | * Calls the {@code flush()} method of an underlying |
| 625 | * {@code FlushableFileOperations} instance, if any. |
| 626 | * |
| 627 | * @throws IOException if reading or writing fails. |
| 628 | */ |
| 629 | public void flush() throws IOException |
| 630 | { |
| 631 | this.assertNotClosed(); |
| 632 | |
| 633 | if ( this.getFileOperations() instanceof FlushableFileOperations ) |
| 634 | { |
| 635 | ( (FlushableFileOperations) this.getFileOperations() ).flush(); |
| 636 | } |
| 637 | } |
| 638 | |
| 639 | /** |
| 640 | * Checks arguments provided to the {@code readBlock} and {@code writeBlock} |
| 641 | * methods. |
| 642 | * |
| 643 | * @throws NullPointerException if {@code buf} is {@code null}. |
| 644 | * @throws IndexOutOfBoundsException if {@code block} is negative, |
| 645 | * greater than or equal to {@code getBlockCount()}, or {@code off} is |
| 646 | * negative, greater than or equal to {@code getBlockSize()}, or |
| 647 | * {@code index} is negative, greater than or equal to the length of |
| 648 | * {@code buf}, or {@code length} is negative or greater than the |
| 649 | * length of {@code buf} minus {@code index} or greater than |
| 650 | * {@code getBlockSize() minus {@code off}. |
| 651 | */ |
| 652 | private void assertValidArguments( final long block, final int off, |
| 653 | final byte[] buf, final int index, |
| 654 | final int length ) throws |
| 655 | NullPointerException, IndexOutOfBoundsException, IOException |
| 656 | { |
| 657 | final long blockCount = this.getBlockCount(); |
| 658 | |
| 659 | if ( buf == null ) |
| 660 | { |
| 661 | throw new NullPointerException( "buf" ); |
| 662 | } |
| 663 | if ( block < 0 || block >= blockCount ) |
| 664 | { |
| 665 | throw new ArrayIndexOutOfBoundsException( (int) block ); |
| 666 | } |
| 667 | if ( off < 0 || off >= this.getBlockSize() ) |
| 668 | { |
| 669 | throw new ArrayIndexOutOfBoundsException( off ); |
| 670 | } |
| 671 | if ( index < 0 || index >= buf.length ) |
| 672 | { |
| 673 | throw new ArrayIndexOutOfBoundsException( index ); |
| 674 | } |
| 675 | if ( length < 0L || length > buf.length - index || |
| 676 | length > this.getBlockSize() - off ) |
| 677 | { |
| 678 | throw new ArrayIndexOutOfBoundsException( length ); |
| 679 | } |
| 680 | } |
| 681 | |
| 682 | /** |
| 683 | * Checks the length of the provided {@code FileOperations} implementation |
| 684 | * against property {@code blockSize}. |
| 685 | * |
| 686 | * @throws IllegalArgumentException if the combination of property |
| 687 | * {@code blockSize} and {@code getFileOperations().getLength()} is invalid. |
| 688 | * @throws IOException if the getting the length fails. |
| 689 | */ |
| 690 | private void assertValidFileLength() throws IOException |
| 691 | { |
| 692 | if ( this.getFileOperations() != null && |
| 693 | this.getFileOperations().getLength() % this.getBlockSize() != 0L ) |
| 694 | { |
| 695 | throw new IllegalArgumentException( |
| 696 | Long.toString( this.getFileOperations().getLength() % |
| 697 | this.getBlockSize() ) ); |
| 698 | |
| 699 | } |
| 700 | } |
| 701 | |
| 702 | /** |
| 703 | * Checks that the instance is not closed. |
| 704 | * |
| 705 | * @throws IOException if the instance is closed. |
| 706 | */ |
| 707 | private void assertNotClosed() throws IOException |
| 708 | { |
| 709 | if ( this.closed ) |
| 710 | { |
| 711 | throw new IOException( this.getAlreadyClosedMessage( |
| 712 | this.getLocale() ) ); |
| 713 | |
| 714 | } |
| 715 | } |
| 716 | |
| 717 | /** |
| 718 | * Gets the value of property {@code monitoringThreshold}. |
| 719 | * |
| 720 | * @return the mininum number of bytes to copy to start any task monitoring. |
| 721 | */ |
| 722 | public int getMonitoringThreshold() |
| 723 | { |
| 724 | if ( this.monitoringThreshold == null ) |
| 725 | { |
| 726 | this.monitoringThreshold = this.getDefaultMonitoringThreshold(); |
| 727 | } |
| 728 | |
| 729 | return this.monitoringThreshold.intValue(); |
| 730 | } |
| 731 | |
| 732 | /** |
| 733 | * Notifies all registered {@code StructuredFileListener}s about inserted |
| 734 | * blocks. |
| 735 | * |
| 736 | * @param index the index new blocks were inserted. |
| 737 | * @param insertedBlocks the number of blocks which were inserted at |
| 738 | * {@code index}. |
| 739 | * |
| 740 | * @throws IOException if reading or writing fails. |
| 741 | */ |
| 742 | private void fireBlocksInserted( |
| 743 | final long index, final long insertedBlocks ) throws IOException |
| 744 | { |
| 745 | final Object[] listeners = this.fileListeners.getListenerList(); |
| 746 | for ( int i = listeners.length - 2; i >= 0; i -= 2 ) |
| 747 | { |
| 748 | if ( listeners[i] == StructuredFileListener.class ) |
| 749 | { |
| 750 | ( (StructuredFileListener) listeners[i + 1] ).blocksInserted( |
| 751 | index, insertedBlocks ); |
| 752 | |
| 753 | } |
| 754 | } |
| 755 | } |
| 756 | |
| 757 | /** |
| 758 | * Notifies all registered {@code StructuredFileListener}s about deleted |
| 759 | * blocks. |
| 760 | * |
| 761 | * @param index the index blocks were deleted at. |
| 762 | * @param deletedBlocks the number of blocks which were deleted starting at |
| 763 | * {@code index}. |
| 764 | * |
| 765 | * @throws IOException if reading or writing fails. |
| 766 | */ |
| 767 | private void fireBlocksDeleted( |
| 768 | final long index, final long deletedBlocks ) throws IOException |
| 769 | { |
| 770 | final Object[] listeners = this.fileListeners.getListenerList(); |
| 771 | for ( int i = listeners.length - 2; i >= 0; i -= 2 ) |
| 772 | { |
| 773 | if ( listeners[i] == StructuredFileListener.class ) |
| 774 | { |
| 775 | ( (StructuredFileListener) listeners[i + 1] ).blocksDeleted( |
| 776 | index, deletedBlocks ); |
| 777 | |
| 778 | } |
| 779 | } |
| 780 | } |
| 781 | |
| 782 | private byte[] getBuffer( final int requested ) throws IOException |
| 783 | { |
| 784 | final long length = this.getFileOperations().getLength(); |
| 785 | |
| 786 | if ( requested <= 0 || requested > length ) |
| 787 | { |
| 788 | throw new IllegalArgumentException( Integer.toString( requested ) ); |
| 789 | } |
| 790 | |
| 791 | if ( this.defaultBuffer == null ) |
| 792 | { |
| 793 | this.defaultBuffer = this.getMemoryManager(). |
| 794 | allocateBytes( this.getDefaultBufferSize() ); |
| 795 | |
| 796 | } |
| 797 | |
| 798 | return requested <= this.defaultBuffer.length || |
| 799 | this.getMemoryManager().getAvailableBytes() < requested |
| 800 | ? this.defaultBuffer |
| 801 | : this.getMemoryManager().allocateBytes( requested ); |
| 802 | |
| 803 | } |
| 804 | |
| 805 | //------------------------------------------------StructuredFileOperations-- |
| 806 | //--Messages---------------------------------------------------------------- |
| 807 | |
| 808 | // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausMessages |
| 809 | // This section is managed by jdtaus-container-mojo. |
| 810 | |
| 811 | /** |
| 812 | * Gets the text of message <code>alreadyClosed</code>. |
| 813 | * <blockquote><pre>Instanz geschlossen - keine E/A-Operationen möglich.</pre></blockquote> |
| 814 | * <blockquote><pre>Instance closed - cannot perform I/O.</pre></blockquote> |
| 815 | * |
| 816 | * @param locale The locale of the message instance to return. |
| 817 | * |
| 818 | * @return Message stating that an instance is already closed. |
| 819 | */ |
| 820 | private String getAlreadyClosedMessage( final Locale locale ) |
| 821 | { |
| 822 | return ContainerFactory.getContainer(). |
| 823 | getMessage( this, "alreadyClosed", locale, null ); |
| 824 | |
| 825 | } |
| 826 | |
| 827 | // </editor-fold>//GEN-END:jdtausMessages |
| 828 | |
| 829 | //----------------------------------------------------------------Messages-- |
| 830 | } |