1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64 public final class StructuredFileOperations implements StructuredFile
65 {
66
67
68
69 private byte[] defaultBuffer;
70
71
72 private long cachedBlockCount = NO_CACHED_BLOCKCOUNT;
73
74 private static final long NO_CACHED_BLOCKCOUNT = Long.MIN_VALUE;
75
76
77 private final EventListenerList fileListeners = new EventListenerList();
78
79
80 private final BigDecimal decimalBlockSize;
81
82
83
84
85
86
87
88
89
90
91
92
93 private MemoryManager getMemoryManager()
94 {
95 return (MemoryManager) ContainerFactory.getContainer().
96 getDependency( this, "MemoryManager" );
97
98 }
99
100
101
102
103
104
105 private Locale getLocale()
106 {
107 return (Locale) ContainerFactory.getContainer().
108 getDependency( this, "Locale" );
109
110 }
111
112
113
114
115
116
117 private TaskMonitor getTaskMonitor()
118 {
119 return (TaskMonitor) ContainerFactory.getContainer().
120 getDependency( this, "TaskMonitor" );
121
122 }
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137 private java.lang.Integer getDefaultMonitoringThreshold()
138 {
139 return (java.lang.Integer) ContainerFactory.getContainer().
140 getProperty( this, "defaultMonitoringThreshold" );
141
142 }
143
144
145
146
147
148
149 private int getDefaultBufferSize()
150 {
151 return ( (java.lang.Integer) ContainerFactory.getContainer().
152 getProperty( this, "defaultBufferSize" ) ).intValue();
153
154 }
155
156
157
158
159
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
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
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
216 this.cachedBlockCount = NO_CACHED_BLOCKCOUNT;
217
218
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
255
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
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
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
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
338 this.cachedBlockCount = NO_CACHED_BLOCKCOUNT;
339
340
341 this.getFileOperations().setLength( this.getFileOperations().
342 getLength() + this.getBlockSize() *
343 count );
344
345
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
378
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
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
481
482
483
484
485
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
516
517
518
519 private int blockSize;
520
521
522 private Integer monitoringThreshold;
523
524
525 private FileOperations fileOperations;
526
527
528 private boolean closed;
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
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
568
569
570
571
572
573
574
575
576
577
578
579
580
581
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
613
614
615
616
617
618 public FileOperations getFileOperations()
619 {
620 return this.fileOperations;
621 }
622
623
624
625
626
627
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
641
642
643
644
645
646
647
648
649
650
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
684
685
686
687
688
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
704
705
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
719
720
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
734
735
736
737
738
739
740
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
759
760
761
762
763
764
765
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
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820 private String getAlreadyClosedMessage( final Locale locale )
821 {
822 return ContainerFactory.getContainer().
823 getMessage( this, "alreadyClosed", locale, null );
824
825 }
826
827
828
829
830 }