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 ( final Iterator it = this.root.entrySet().iterator();
461 it.hasNext(); )
462 {
463 final Map.Entry entry = (Map.Entry) it.next();
464 final long block = ( (Long) entry.getKey() ).longValue();
465 final Node current = (Node) entry.getValue();
466
467
468 if ( current.length == FileOperations.EOF ||
469 current.cacheIndex == Node.NO_CACHEINDEX )
470 {
471 continue;
472 }
473
474 assert current.block == block : "Unexpected cache state.";
475
476 if ( previous == null )
477 {
478 previous = current;
479 startPos = current.block * this.getBlockSize();
480 startIndex = current.cacheIndex;
481 length = current.length;
482 dirty = current.dirty;
483 }
484 else if ( current.block == previous.block + 1L )
485 {
486
487 assert current.cacheIndex == previous.cacheIndex +
488 this.getBlockSize() :
489 "Unexpected cache state.";
490
491 previous = current;
492 length += current.length;
493 if ( !dirty )
494 {
495 dirty = current.dirty;
496 }
497 }
498 else
499 {
500 if ( dirty )
501 {
502 this.getFileOperations().setFilePointer( startPos );
503 this.getFileOperations().write(
504 this.getCache(), startIndex, length );
505
506 }
507
508 previous = current;
509 startPos = current.block * this.getBlockSize();
510 startIndex = current.cacheIndex;
511 length = current.length;
512 dirty = current.dirty;
513 }
514 }
515
516 if ( dirty )
517 {
518 this.getFileOperations().setFilePointer( startPos );
519 this.getFileOperations().write(
520 this.getCache(), startIndex, length );
521
522 }
523
524
525 for ( final Iterator it = this.root.entrySet().iterator();
526 it.hasNext(); )
527 {
528 final Map.Entry entry = (Map.Entry) it.next();
529 final Node current = (Node) entry.getValue();
530
531 current.cacheIndex = Node.NO_CACHEINDEX;
532 current.dirty = false;
533 }
534
535 this.nextCacheIndex = 0;
536
537 if ( this.getFileOperations() instanceof FlushableFileOperations )
538 {
539 ( (FlushableFileOperations) this.getFileOperations() ).flush();
540 }
541 }
542
543
544
545
546
547 private static final class Node
548 {
549
550 private static final int NO_CACHEINDEX = Integer.MIN_VALUE;
551
552 private Node()
553 {
554 super();
555 }
556
557 private long block;
558
559 private int cacheIndex = NO_CACHEINDEX;
560
561 private int length;
562
563 private boolean dirty;
564
565 }
566
567
568 private final FileOperations fileOperations;
569
570
571 private byte[] cache;
572
573
574 private byte[] defragCache;
575
576
577 private int nextCacheIndex;
578
579
580 private final Map root = new TreeMap();
581
582
583 private long filePointer;
584
585
586 private long cachedFilePointerBlock = NO_FILEPOINTERBLOCK;
587
588 private long cachedFilePointerBlockStart = FileOperations.EOF;
589
590 private static final long NO_FILEPOINTERBLOCK = Long.MIN_VALUE;
591
592
593 private boolean closed;
594
595
596 private Integer blockSize;
597
598
599 private Integer cacheBlocks;
600
601
602
603
604
605
606
607
608
609
610 public CoalescingFileOperations( final FileOperations fileOperations )
611 throws IOException
612 {
613 super();
614
615 if ( fileOperations == null )
616 {
617 throw new NullPointerException( "fileOperations" );
618 }
619
620 this.fileOperations = fileOperations;
621 this.filePointer = fileOperations.getFilePointer();
622 }
623
624
625
626
627
628
629
630
631
632
633
634
635 public CoalescingFileOperations( final FileOperations fileOperations,
636 final int blockSize ) throws IOException
637 {
638 this( fileOperations );
639 if ( blockSize > 0 )
640 {
641 this.blockSize = new Integer( blockSize );
642 }
643 }
644
645
646
647
648
649
650
651
652
653
654
655
656
657 public CoalescingFileOperations( final FileOperations fileOperations,
658 final int blockSize,
659 final int cacheBlocks )
660 throws IOException
661 {
662 this( fileOperations, blockSize );
663 if ( cacheBlocks > 0 )
664 {
665 this.cacheBlocks = new Integer( cacheBlocks );
666 }
667 }
668
669
670
671
672
673
674
675
676 public FileOperations getFileOperations()
677 {
678 return this.fileOperations;
679 }
680
681
682
683
684
685
686 public int getBlockSize()
687 {
688 if ( this.blockSize == null )
689 {
690 this.blockSize = this.getDefaultBlockSize();
691 }
692
693 return this.blockSize.intValue();
694 }
695
696
697
698
699
700
701 public int getCacheBlocks()
702 {
703 if ( this.cacheBlocks == null )
704 {
705 this.cacheBlocks = this.getDefaultCacheBlocks();
706 }
707
708 return this.cacheBlocks.intValue();
709 }
710
711
712
713
714
715
716 private byte[] getCache()
717 {
718 if ( this.cache == null )
719 {
720 this.cache = this.getMemoryManager().allocateBytes(
721 this.getBlockSize() * this.getCacheBlocks() );
722
723 }
724
725 return this.cache;
726 }
727
728
729
730
731
732
733 private byte[] getDefragCache()
734 {
735 if ( this.defragCache == null )
736 {
737 this.defragCache = this.getMemoryManager().allocateBytes(
738 this.getBlockSize() * this.getCacheBlocks() );
739
740 }
741
742 return this.defragCache;
743 }
744
745
746
747
748
749
750
751
752
753 private long getFilePointerBlock( final long filePointer )
754 {
755 if ( this.cachedFilePointerBlock == NO_FILEPOINTERBLOCK )
756 {
757 this.cachedFilePointerBlock =
758 ( filePointer / this.getBlockSize() ) -
759 ( ( filePointer % this.getBlockSize() ) / this.getBlockSize() );
760
761 this.cachedFilePointerBlockStart =
762 this.cachedFilePointerBlock * this.getBlockSize();
763
764 }
765 else
766 {
767 if ( !( filePointer >= this.cachedFilePointerBlockStart &&
768 filePointer <= this.cachedFilePointerBlockStart +
769 this.getBlockSize() ) )
770 {
771 this.cachedFilePointerBlock =
772 ( filePointer / this.getBlockSize() ) -
773 ( ( filePointer % this.getBlockSize() ) /
774 this.getBlockSize() );
775
776 this.cachedFilePointerBlockStart =
777 this.cachedFilePointerBlock * this.getBlockSize();
778
779 }
780 }
781
782 return this.cachedFilePointerBlock;
783 }
784
785
786
787
788
789
790
791
792
793
794
795
796
797 private Node[] getCacheNodesForLength( final long filePointer,
798 final int length )
799 {
800 final long startingBlock = this.getFilePointerBlock( filePointer );
801 final long endingBlock =
802 this.getFilePointerBlock( filePointer + length );
803
804 assert endingBlock - startingBlock <= Integer.MAX_VALUE :
805 "Unexpected implementation limit reached.";
806
807 final Node[] nodes =
808 new Node[ (int) ( endingBlock - startingBlock + 1L ) ];
809
810 if ( startingBlock == endingBlock )
811 {
812 nodes[0] = this.getCacheNode( startingBlock );
813 }
814 else
815 {
816 int i;
817 long block;
818
819 for ( block = startingBlock, i = 0; block <= endingBlock;
820 block++, i++ )
821 {
822 nodes[i] = this.getCacheNode( block );
823 }
824 }
825
826 return nodes;
827 }
828
829
830
831
832
833
834
835
836
837
838
839
840 private void fillCache( final Node[] nodes ) throws IOException
841 {
842 if ( nodes == null )
843 {
844 throw new NullPointerException( "nodes" );
845 }
846
847
848
849 long neededBytes = 0L;
850 for ( int i = nodes.length - 1; i >= 0; i-- )
851 {
852 if ( nodes[i].cacheIndex == Node.NO_CACHEINDEX )
853 {
854 neededBytes += this.getBlockSize();
855 }
856 }
857
858 if ( this.nextCacheIndex + neededBytes > this.getCache().length )
859 {
860 this.flush();
861 }
862
863
864
865 for ( int i = nodes.length - 1; i >= 0; i-- )
866 {
867 if ( nodes[i].cacheIndex == Node.NO_CACHEINDEX &&
868 this.nextCacheIndex < this.getCache().length )
869 {
870
871
872
873 final long pos = nodes[i].block * this.getBlockSize();
874 if ( pos > this.getLength() )
875 {
876 nodes[i].length = FileOperations.EOF;
877 continue;
878 }
879 else if ( pos + this.getBlockSize() > this.getLength() )
880 {
881 final long delta = this.getLength() - pos;
882
883 assert delta <= Integer.MAX_VALUE :
884 "Unexpected implementation limit reached.";
885
886 nodes[i].length = (int) delta;
887 }
888 else
889 {
890 nodes[i].length = this.getBlockSize();
891 }
892
893
894 nodes[i].cacheIndex = this.nextCacheIndex;
895 this.nextCacheIndex += this.getBlockSize();
896
897
898 int read = FileOperations.EOF;
899 int totalRead = 0;
900 int toRead = nodes[i].length;
901 this.getFileOperations().setFilePointer( pos );
902
903 do
904 {
905 read = this.getFileOperations().read(
906 this.getCache(), nodes[i].cacheIndex + totalRead,
907 toRead );
908
909 assert read != FileOperations.EOF :
910 "Unexpected end of file.";
911
912 totalRead += read;
913 toRead -= read;
914
915 }
916 while ( totalRead < nodes[i].length );
917 }
918 }
919 }
920
921
922 private void defragmentCache()
923 {
924 int defragIndex = 0;
925
926
927 for ( final Iterator it = this.root.entrySet().iterator();
928 it.hasNext(); )
929 {
930 final Map.Entry entry = (Map.Entry) it.next();
931 final long block = ( (Long) entry.getKey() ).longValue();
932 final Node current = (Node) entry.getValue();
933
934
935 if ( current.length == FileOperations.EOF ||
936 current.cacheIndex == Node.NO_CACHEINDEX )
937 {
938 continue;
939 }
940
941 assert current.block == block : "Unexpected cache state.";
942
943 System.arraycopy( this.getCache(), current.cacheIndex,
944 this.getDefragCache(), defragIndex,
945 this.getBlockSize() );
946
947 current.cacheIndex = defragIndex;
948 defragIndex += this.getBlockSize();
949 }
950
951 System.arraycopy( this.getDefragCache(), 0, this.getCache(), 0,
952 this.getCache().length );
953
954 }
955
956
957
958
959
960
961
962
963 private Node getCacheNode( final long block )
964 {
965 final Long key = new Long( block );
966 Node node = (Node) this.root.get( key );
967 if ( node == null )
968 {
969 node = new Node();
970 node.block = block;
971 this.root.put( key, node );
972 }
973
974 return node;
975 }
976
977
978
979
980
981
982 private void assertNotClosed() throws IOException
983 {
984 if ( this.closed )
985 {
986 throw new IOException( this.getAlreadyClosedMessage(
987 this.getLocale() ) );
988
989 }
990 }
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010 private String getReadBypassesCacheMessage( final Locale locale,
1011 final java.lang.Number blockSize,
1012 final java.lang.Number cacheBlocks,
1013 final java.lang.Number uncachedBytes )
1014 {
1015 return ContainerFactory.getContainer().
1016 getMessage( this, "readBypassesCache", locale,
1017 new Object[]
1018 {
1019 blockSize,
1020 cacheBlocks,
1021 uncachedBytes
1022 });
1023
1024 }
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038 private String getWriteBypassesCacheMessage( final Locale locale,
1039 final java.lang.Number blockSize,
1040 final java.lang.Number cacheBlocks,
1041 final java.lang.Number uncachedBytes )
1042 {
1043 return ContainerFactory.getContainer().
1044 getMessage( this, "writeBypassesCache", locale,
1045 new Object[]
1046 {
1047 blockSize,
1048 cacheBlocks,
1049 uncachedBytes
1050 });
1051
1052 }
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063 private String getAlreadyClosedMessage( final Locale locale )
1064 {
1065 return ContainerFactory.getContainer().
1066 getMessage( this, "alreadyClosed", locale, null );
1067
1068 }
1069
1070
1071
1072
1073 }