| 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 | } |