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.io.InputStream;
25 import java.io.OutputStream;
26 import java.util.Iterator;
27 import java.util.Locale;
28 import java.util.Map;
29 import java.util.TreeMap;
30 import org.jdtaus.core.container.ContainerFactory;
31 import org.jdtaus.core.io.FileOperations;
32 import org.jdtaus.core.lang.spi.MemoryManager;
33 import org.jdtaus.core.logging.spi.Logger;
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 public final class CoalescingFileOperations implements FlushableFileOperations
54 {
55
56
57
58
59
60
61
62
63
64
65 private MemoryManager getMemoryManager()
66 {
67 return (MemoryManager) ContainerFactory.getContainer().
68 getDependency( this, "MemoryManager" );
69
70 }
71
72
73
74
75
76
77 private Locale getLocale()
78 {
79 return (Locale) ContainerFactory.getContainer().
80 getDependency( this, "Locale" );
81
82 }
83
84
85
86
87
88
89 private Logger getLogger()
90 {
91 return (Logger) ContainerFactory.getContainer().
92 getDependency( this, "Logger" );
93
94 }
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109 private java.lang.Integer getDefaultCacheBlocks()
110 {
111 return (java.lang.Integer) ContainerFactory.getContainer().
112 getProperty( this, "defaultCacheBlocks" );
113
114 }
115
116
117
118
119
120
121 private java.lang.Integer getDefaultBlockSize()
122 {
123 return (java.lang.Integer) ContainerFactory.getContainer().
124 getProperty( this, "defaultBlockSize" );
125
126 }
127
128
129
130
131
132
133 public long getLength() throws IOException
134 {
135 this.assertNotClosed();
136
137 return this.getFileOperations().getLength();
138 }
139
140 public void setLength( final long newLength ) throws IOException
141 {
142 this.assertNotClosed();
143
144
145 final long oldLength = this.getLength();
146 if ( newLength > oldLength )
147 {
148 final long delta = newLength - oldLength;
149
150 assert delta <= Integer.MAX_VALUE :
151 "Unexpected implementation limit reached.";
152
153 final Node[] nodes =
154 this.getCacheNodesForLength( oldLength, (int) delta );
155
156 for ( int i = 0; i < nodes.length; i++ )
157 {
158 final long startPos = nodes[i].block * this.getBlockSize();
159 final long blockDelta = newLength - startPos;
160
161 assert blockDelta <= Integer.MAX_VALUE :
162 "Unexpected implementation limit reached.";
163
164 nodes[i].length = blockDelta >= this.getBlockSize()
165 ? this.getBlockSize()
166 : (int) blockDelta;
167
168 }
169 }
170 else if ( newLength < oldLength )
171 {
172 final long delta = oldLength - newLength;
173
174 assert delta <= Integer.MAX_VALUE :
175 "Unexpected implementation limit reached.";
176
177 final Node[] nodes =
178 this.getCacheNodesForLength( newLength, (int) delta );
179
180 for ( int i = 0; i < nodes.length; i++ )
181 {
182 final long startPos = nodes[i].block * this.getBlockSize();
183 if ( startPos > newLength )
184 {
185 this.root.remove( new Long( nodes[i].block ) );
186 }
187 else
188 {
189 final long blockDelta = newLength - startPos;
190
191 assert blockDelta <= Integer.MAX_VALUE :
192 "Unexpected implementation limit reached.";
193
194 nodes[i].length = blockDelta >= this.getBlockSize()
195 ? this.getBlockSize()
196 : (int) blockDelta;
197
198 }
199 }
200 }
201
202 this.getFileOperations().setLength( newLength );
203
204 if ( this.filePointer > newLength )
205 {
206 this.filePointer = newLength;
207 }
208 }
209
210 public long getFilePointer() throws IOException
211 {
212 this.assertNotClosed();
213
214 return this.filePointer;
215 }
216
217 public void setFilePointer( final long pos ) throws IOException
218 {
219 this.assertNotClosed();
220
221 this.filePointer = pos;
222 }
223
224 public int read( final byte[] buf, int off, int len ) throws IOException
225 {
226 if ( buf == null )
227 {
228 throw new NullPointerException( "buf" );
229 }
230 if ( off < 0 )
231 {
232 throw new IndexOutOfBoundsException( Integer.toString( off ) );
233 }
234 if ( len < 0 )
235 {
236 throw new IndexOutOfBoundsException( Integer.toString( len ) );
237 }
238 if ( off + len > buf.length )
239 {
240 throw new IndexOutOfBoundsException( Integer.toString( off + len ) );
241 }
242
243 this.assertNotClosed();
244
245 int read = FileOperations.EOF;
246
247 if ( len == 0 )
248 {
249 read = 0;
250 }
251 else if ( this.filePointer < this.getLength() )
252 {
253 final Node[] nodes =
254 this.getCacheNodesForLength( this.filePointer, len );
255
256
257 this.fillCache( nodes );
258
259 int copied = 0;
260 for ( int i = 0; i < nodes.length; i++ )
261 {
262 if ( nodes[i].length == FileOperations.EOF )
263 {
264 continue;
265 }
266
267 if ( nodes[i].cacheIndex != Node.NO_CACHEINDEX )
268 {
269
270
271 final long delta =
272 nodes[i].cacheIndex +
273 ( this.filePointer - nodes[i].block *
274 this.getBlockSize() );
275
276 assert delta <= Integer.MAX_VALUE :
277 "Unexpected implementation limit reached.";
278
279 final int blockOffset = (int) delta;
280 final int blockDelta = nodes[i].length -
281 ( blockOffset - nodes[i].cacheIndex );
282
283 final int copyLength = len > blockDelta
284 ? blockDelta
285 : len;
286
287 System.arraycopy( this.getCache(), blockOffset, buf, off,
288 copyLength );
289
290 off += copyLength;
291 len -= copyLength;
292 copied += copyLength;
293 this.filePointer += copyLength;
294 }
295 else
296 {
297 this.getFileOperations().setFilePointer( this.filePointer );
298 copied += this.getFileOperations().read( buf, off, len );
299 this.filePointer += len;
300
301 this.getLogger().debug(
302 this.getReadBypassesCacheMessage(
303 this.getLocale(),
304 new Integer( this.getBlockSize() ),
305 new Integer( this.getCacheBlocks() ),
306 new Integer( len ) ) );
307
308
309 break;
310 }
311 }
312
313 read = copied;
314 }
315
316 return read;
317 }
318
319 public void write( final byte[] buf, int off, int len ) throws IOException
320 {
321 if ( buf == null )
322 {
323 throw new NullPointerException( "buf" );
324 }
325 if ( off < 0 )
326 {
327 throw new IndexOutOfBoundsException( Integer.toString( off ) );
328 }
329 if ( len < 0 )
330 {
331 throw new IndexOutOfBoundsException( Integer.toString( len ) );
332 }
333 if ( off + len > buf.length )
334 {
335 throw new IndexOutOfBoundsException( Integer.toString( off + len ) );
336 }
337
338 this.assertNotClosed();
339
340 if ( this.filePointer + len > this.getLength() )
341 {
342 this.setLength( this.filePointer + len );
343 }
344
345 final Node[] nodes =
346 this.getCacheNodesForLength( this.filePointer, len );
347
348
349 this.fillCache( nodes );
350
351 for ( int i = 0; i < nodes.length; i++ )
352 {
353
354 assert nodes[i].length != FileOperations.EOF :
355 "Unexpected cache state.";
356
357 if ( nodes[i].cacheIndex != Node.NO_CACHEINDEX )
358 {
359
360
361 final long delta = nodes[i].cacheIndex +
362 ( this.filePointer - nodes[i].block *
363 this.getBlockSize() );
364
365 assert delta <= Integer.MAX_VALUE :
366 "Unexpected implementation limit reached.";
367
368 final int blockOffset = (int) delta;
369 final int blockDelta = nodes[i].length -
370 ( blockOffset - nodes[i].cacheIndex );
371
372 final int copyLength = len > blockDelta
373 ? blockDelta
374 : len;
375
376 System.arraycopy( buf, off, this.getCache(), blockOffset,
377 copyLength );
378
379 off += copyLength;
380 len -= copyLength;
381 this.filePointer += copyLength;
382 nodes[i].dirty = true;
383 }
384 else
385 {
386 this.getFileOperations().setFilePointer( this.filePointer );
387 this.getFileOperations().write( buf, off, len );
388 this.filePointer += len;
389
390 this.getLogger().debug(
391 this.getWriteBypassesCacheMessage(
392 this.getLocale(),
393 new Integer( this.getBlockSize() ),
394 new Integer( this.getCacheBlocks() ),
395 new Integer( len ) ) );
396
397
398 break;
399 }
400 }
401 }
402
403 public void read( final OutputStream out ) throws IOException
404 {
405 this.assertNotClosed();
406
407 this.getFileOperations().read( out );
408 this.filePointer = this.getFileOperations().getFilePointer();
409 }
410
411 public void write( final InputStream in ) throws IOException
412 {
413 this.assertNotClosed();
414
415 this.getFileOperations().write( in );
416 this.filePointer = this.getFileOperations().getFilePointer();
417 }
418
419
420
421
422
423
424
425
426
427
428 public void close() throws IOException
429 {
430 this.assertNotClosed();
431
432 this.flush();
433 this.getFileOperations().close();
434 this.closed = true;
435 }
436
437
438
439
440
441
442
443
444
445
446
447
448 public void flush() throws IOException
449 {
450 this.assertNotClosed();
451
452 this.defragmentCache();
453
454 long startPos = FileOperations.EOF;
455 int startIndex = FileOperations.EOF;
456 int length = FileOperations.EOF;
457 Node previous = null;
458 boolean dirty = false;
459
460 for ( Iterator it = this.root.entrySet().iterator(); it.hasNext(); )
461 {
462 final Map.Entry entry = (Map.Entry) it.next();
463 final long block = ( (Long) entry.getKey() ).longValue();
464 final Node current = (Node) entry.getValue();
465
466
467 if ( current.length == FileOperations.EOF ||
468 current.cacheIndex == Node.NO_CACHEINDEX )
469 {
470 continue;
471 }
472
473 assert current.block == block : "Unexpected cache state.";
474
475 if ( previous == null )
476 {
477 previous = current;
478 startPos = current.block * this.getBlockSize();
479 startIndex = current.cacheIndex;
480 length = current.length;
481 dirty = current.dirty;
482 }
483 else if ( current.block == previous.block + 1L )
484 {
485
486 assert current.cacheIndex == previous.cacheIndex +
487 this.getBlockSize() :
488 "Unexpected cache state.";
489
490 previous = current;
491 length += current.length;
492 if ( !dirty )
493 {
494 dirty = current.dirty;
495 }
496 }
497 else
498 {
499 if ( dirty )
500 {
501 this.getFileOperations().setFilePointer( startPos );
502 this.getFileOperations().write(
503 this.getCache(), startIndex, length );
504
505 }
506
507 previous = current;
508 startPos = current.block * this.getBlockSize();
509 startIndex = current.cacheIndex;
510 length = current.length;
511 dirty = current.dirty;
512 }
513 }
514
515 if ( dirty )
516 {
517 this.getFileOperations().setFilePointer( startPos );
518 this.getFileOperations().write(
519 this.getCache(), startIndex, length );
520
521 }
522
523
524 for ( Iterator it = this.root.entrySet().iterator(); it.hasNext(); )
525 {
526 final Map.Entry entry = (Map.Entry) it.next();
527 final Node current = (Node) entry.getValue();
528
529 current.cacheIndex = Node.NO_CACHEINDEX;
530 current.dirty = false;
531 }
532
533 this.nextCacheIndex = 0;
534
535 if ( this.getFileOperations() instanceof FlushableFileOperations )
536 {
537 ( (FlushableFileOperations) this.getFileOperations() ).flush();
538 }
539 }
540
541
542
543
544
545 private static final class Node
546 {
547
548 private static final int NO_CACHEINDEX = Integer.MIN_VALUE;
549
550 long block;
551
552 int cacheIndex = NO_CACHEINDEX;
553
554 int length;
555
556 boolean dirty;
557
558 }
559
560
561 private final FileOperations fileOperations;
562
563
564 private byte[] cache;
565
566
567 private byte[] defragCache;
568
569
570 private int nextCacheIndex;
571
572
573 private final Map root = new TreeMap();
574
575
576 private long filePointer;
577
578
579 private long cachedFilePointerBlock = NO_FILEPOINTERBLOCK;
580
581 private long cachedFilePointerBlockStart = FileOperations.EOF;
582
583 private static final long NO_FILEPOINTERBLOCK = Long.MIN_VALUE;
584
585
586 private boolean closed;
587
588
589 private Integer blockSize;
590
591
592 private Integer cacheBlocks;
593
594
595
596
597
598
599
600
601
602
603 public CoalescingFileOperations( final FileOperations fileOperations )
604 throws IOException
605 {
606 super();
607
608 if ( fileOperations == null )
609 {
610 throw new NullPointerException( "fileOperations" );
611 }
612
613 this.fileOperations = fileOperations;
614 this.filePointer = fileOperations.getFilePointer();
615 }
616
617
618
619
620
621
622
623
624
625
626
627
628 public CoalescingFileOperations( final FileOperations fileOperations,
629 final int blockSize ) throws IOException
630 {
631 this( fileOperations );
632 if ( blockSize > 0 )
633 {
634 this.blockSize = new Integer( blockSize );
635 }
636 }
637
638
639
640
641
642
643
644
645
646
647
648
649
650 public CoalescingFileOperations( final FileOperations fileOperations,
651 final int blockSize,
652 final int cacheBlocks )
653 throws IOException
654 {
655 this( fileOperations, blockSize );
656 if ( cacheBlocks > 0 )
657 {
658 this.cacheBlocks = new Integer( cacheBlocks );
659 }
660 }
661
662
663
664
665
666
667
668
669 public FileOperations getFileOperations()
670 {
671 return this.fileOperations;
672 }
673
674
675
676
677
678
679 public int getBlockSize()
680 {
681 if ( this.blockSize == null )
682 {
683 this.blockSize = this.getDefaultBlockSize();
684 }
685
686 return this.blockSize.intValue();
687 }
688
689
690
691
692
693
694 public int getCacheBlocks()
695 {
696 if ( this.cacheBlocks == null )
697 {
698 this.cacheBlocks = this.getDefaultCacheBlocks();
699 }
700
701 return this.cacheBlocks.intValue();
702 }
703
704
705
706
707
708
709 private byte[] getCache()
710 {
711 if ( this.cache == null )
712 {
713 this.cache = this.getMemoryManager().allocateBytes(
714 this.getBlockSize() * this.getCacheBlocks() );
715
716 }
717
718 return this.cache;
719 }
720
721
722
723
724
725
726 private byte[] getDefragCache()
727 {
728 if ( this.defragCache == null )
729 {
730 this.defragCache = this.getMemoryManager().allocateBytes(
731 this.getBlockSize() * this.getCacheBlocks() );
732
733 }
734
735 return this.defragCache;
736 }
737
738
739
740
741
742
743
744
745
746 private long getFilePointerBlock( final long filePointer )
747 {
748 if ( this.cachedFilePointerBlock == NO_FILEPOINTERBLOCK )
749 {
750 this.cachedFilePointerBlock =
751 ( filePointer / this.getBlockSize() ) -
752 ( ( filePointer % this.getBlockSize() ) / this.getBlockSize() );
753
754 this.cachedFilePointerBlockStart =
755 this.cachedFilePointerBlock * this.getBlockSize();
756
757 }
758 else
759 {
760 if ( !( filePointer >= this.cachedFilePointerBlockStart &&
761 filePointer <= this.cachedFilePointerBlockStart +
762 this.getBlockSize() ) )
763 {
764 this.cachedFilePointerBlock =
765 ( filePointer / this.getBlockSize() ) -
766 ( ( filePointer % this.getBlockSize() ) /
767 this.getBlockSize() );
768
769 this.cachedFilePointerBlockStart =
770 this.cachedFilePointerBlock * this.getBlockSize();
771
772 }
773 }
774
775 return this.cachedFilePointerBlock;
776 }
777
778
779
780
781
782
783
784
785
786
787
788
789
790 private Node[] getCacheNodesForLength( final long filePointer,
791 final int length )
792 {
793 final long startingBlock = this.getFilePointerBlock( filePointer );
794 final long endingBlock =
795 this.getFilePointerBlock( filePointer + length );
796
797 assert endingBlock - startingBlock <= Integer.MAX_VALUE :
798 "Unexpected implementation limit reached.";
799
800 final Node[] nodes =
801 new Node[ (int) ( endingBlock - startingBlock + 1L ) ];
802
803 if ( startingBlock == endingBlock )
804 {
805 nodes[0] = this.getCacheNode( startingBlock );
806 }
807 else
808 {
809 int i;
810 long block;
811
812 for ( block = startingBlock, i = 0; block <= endingBlock;
813 block++, i++ )
814 {
815 nodes[i] = this.getCacheNode( block );
816 }
817 }
818
819 return nodes;
820 }
821
822
823
824
825
826
827
828
829
830
831
832
833 private void fillCache( final Node[] nodes ) throws IOException
834 {
835 if ( nodes == null )
836 {
837 throw new NullPointerException( "nodes" );
838 }
839
840
841
842 long neededBytes = 0L;
843 for ( int i = nodes.length - 1; i >= 0; i-- )
844 {
845 if ( nodes[i].cacheIndex == Node.NO_CACHEINDEX )
846 {
847 neededBytes += this.getBlockSize();
848 }
849 }
850
851 if ( this.nextCacheIndex + neededBytes > this.getCache().length )
852 {
853 this.flush();
854 }
855
856
857
858 for ( int i = nodes.length - 1; i >= 0; i-- )
859 {
860 if ( nodes[i].cacheIndex == Node.NO_CACHEINDEX &&
861 this.nextCacheIndex < this.getCache().length )
862 {
863
864
865
866 long pos = nodes[i].block * this.getBlockSize();
867 if ( pos > this.getLength() )
868 {
869 nodes[i].length = FileOperations.EOF;
870 continue;
871 }
872 else if ( pos + this.getBlockSize() > this.getLength() )
873 {
874 final long delta = this.getLength() - pos;
875
876 assert delta <= Integer.MAX_VALUE :
877 "Unexpected implementation limit reached.";
878
879 nodes[i].length = (int) delta;
880 }
881 else
882 {
883 nodes[i].length = this.getBlockSize();
884 }
885
886
887 nodes[i].cacheIndex = this.nextCacheIndex;
888 this.nextCacheIndex += this.getBlockSize();
889
890
891 int read = FileOperations.EOF;
892 int totalRead = 0;
893 int toRead = nodes[i].length;
894 this.getFileOperations().setFilePointer( pos );
895
896 do
897 {
898 read = this.getFileOperations().read(
899 this.getCache(), nodes[i].cacheIndex + totalRead,
900 toRead );
901
902 assert read != FileOperations.EOF :
903 "Unexpected end of file.";
904
905 totalRead += read;
906 toRead -= read;
907
908 }
909 while ( totalRead < nodes[i].length );
910 }
911 }
912 }
913
914
915 private void defragmentCache()
916 {
917 int defragIndex = 0;
918
919
920 for ( Iterator it = this.root.entrySet().iterator(); it.hasNext(); )
921 {
922 final Map.Entry entry = (Map.Entry) it.next();
923 final long block = ( (Long) entry.getKey() ).longValue();
924 final Node current = (Node) entry.getValue();
925
926
927 if ( current.length == FileOperations.EOF ||
928 current.cacheIndex == Node.NO_CACHEINDEX )
929 {
930 continue;
931 }
932
933 assert current.block == block : "Unexpected cache state.";
934
935 System.arraycopy( this.getCache(), current.cacheIndex,
936 this.getDefragCache(), defragIndex,
937 this.getBlockSize() );
938
939 current.cacheIndex = defragIndex;
940 defragIndex += this.getBlockSize();
941 }
942
943 System.arraycopy( this.getDefragCache(), 0, this.getCache(), 0,
944 this.getCache().length );
945
946 }
947
948
949
950
951
952
953
954
955 private Node getCacheNode( final long block )
956 {
957 final Long key = new Long( block );
958 Node node = (Node) this.root.get( key );
959 if ( node == null )
960 {
961 node = new Node();
962 node.block = block;
963 this.root.put( key, node );
964 }
965
966 return node;
967 }
968
969
970
971
972
973
974 private void assertNotClosed() throws IOException
975 {
976 if ( this.closed )
977 {
978 throw new IOException( this.getAlreadyClosedMessage(
979 this.getLocale() ) );
980
981 }
982 }
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002 private String getReadBypassesCacheMessage( final Locale locale,
1003 final java.lang.Number blockSize,
1004 final java.lang.Number cacheBlocks,
1005 final java.lang.Number uncachedBytes )
1006 {
1007 return ContainerFactory.getContainer().
1008 getMessage( this, "readBypassesCache", locale,
1009 new Object[]
1010 {
1011 blockSize,
1012 cacheBlocks,
1013 uncachedBytes
1014 });
1015
1016 }
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030 private String getWriteBypassesCacheMessage( final Locale locale,
1031 final java.lang.Number blockSize,
1032 final java.lang.Number cacheBlocks,
1033 final java.lang.Number uncachedBytes )
1034 {
1035 return ContainerFactory.getContainer().
1036 getMessage( this, "writeBypassesCache", locale,
1037 new Object[]
1038 {
1039 blockSize,
1040 cacheBlocks,
1041 uncachedBytes
1042 });
1043
1044 }
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055 private String getAlreadyClosedMessage( final Locale locale )
1056 {
1057 return ContainerFactory.getContainer().
1058 getMessage( this, "alreadyClosed", locale, null );
1059
1060 }
1061
1062
1063
1064
1065 }