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.Locale;
27 import org.jdtaus.core.container.ContainerFactory;
28 import org.jdtaus.core.io.FileOperations;
29 import org.jdtaus.core.lang.spi.MemoryManager;
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 public final class ReadAheadFileOperations implements FlushableFileOperations
49 {
50
51
52
53
54
55
56
57
58
59
60 private MemoryManager getMemoryManager()
61 {
62 return (MemoryManager) ContainerFactory.getContainer().
63 getDependency( this, "MemoryManager" );
64
65 }
66
67
68
69
70
71
72 private Locale getLocale()
73 {
74 return (Locale) ContainerFactory.getContainer().
75 getDependency( this, "Locale" );
76
77 }
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92 private java.lang.Integer getDefaultCacheSize()
93 {
94 return (java.lang.Integer) ContainerFactory.getContainer().
95 getProperty( this, "defaultCacheSize" );
96
97 }
98
99
100
101
102
103
104 public long getLength() throws IOException
105 {
106 this.assertNotClosed();
107
108 return this.fileOperations.getLength();
109 }
110
111 public void setLength( final long newLength ) throws IOException
112 {
113 this.assertNotClosed();
114
115 final long oldLength = this.getLength();
116 this.fileOperations.setLength( newLength );
117 if ( this.filePointer > newLength )
118 {
119 this.filePointer = newLength;
120 }
121
122 if ( oldLength > newLength && this.cachePosition != NO_CACHEPOSITION &&
123 this.cachePosition + this.cacheLength >= newLength )
124 {
125 this.cachePosition = NO_CACHEPOSITION;
126 }
127 }
128
129 public long getFilePointer() throws IOException
130 {
131 this.assertNotClosed();
132
133 return this.filePointer;
134 }
135
136 public void setFilePointer( final long pos ) throws IOException
137 {
138 this.assertNotClosed();
139
140 this.filePointer = pos;
141 }
142
143 public int read( final byte[] buf, int off, int len )
144 throws IOException
145 {
146 if ( buf == null )
147 {
148 throw new NullPointerException( "buf" );
149 }
150 if ( off < 0 )
151 {
152 throw new IndexOutOfBoundsException( Integer.toString( off ) );
153 }
154 if ( len < 0 )
155 {
156 throw new IndexOutOfBoundsException( Integer.toString( len ) );
157 }
158 if ( off + len > buf.length )
159 {
160 throw new IndexOutOfBoundsException( Integer.toString( off + len ) );
161 }
162
163 this.assertNotClosed();
164
165 int read = FileOperations.EOF;
166
167 final long fileLength = this.getLength();
168
169 if ( len == 0 )
170 {
171 read = 0;
172 }
173 else if ( this.filePointer < fileLength )
174 {
175 if ( this.cachePosition == NO_CACHEPOSITION ||
176 !( this.filePointer >= this.cachePosition &&
177 this.filePointer < this.cachePosition + this.cacheLength ) )
178 {
179 this.fillCache();
180 }
181
182 final long cacheStart = this.filePointer - this.cachePosition;
183
184 assert cacheStart <= Integer.MAX_VALUE :
185 "Unexpected implementation limit reached.";
186
187 final int cachedLength = len > this.cacheLength -
188 (int) cacheStart
189 ? this.cacheLength - (int) cacheStart
190 : len;
191
192 System.arraycopy( this.getCache(), (int) cacheStart, buf, off,
193 cachedLength );
194
195 len -= cachedLength;
196 off += cachedLength;
197 read = cachedLength;
198 this.filePointer += cachedLength;
199 }
200
201 return read;
202 }
203
204 public void write( final byte[] buf, final int off, final int len )
205 throws IOException
206 {
207 if ( buf == null )
208 {
209 throw new NullPointerException( "buf" );
210 }
211 if ( off < 0 )
212 {
213 throw new IndexOutOfBoundsException( Integer.toString( off ) );
214 }
215 if ( len < 0 )
216 {
217 throw new IndexOutOfBoundsException( Integer.toString( len ) );
218 }
219 if ( off + len > buf.length )
220 {
221 throw new IndexOutOfBoundsException( Integer.toString( off + len ) );
222 }
223
224 this.assertNotClosed();
225
226 if ( this.cachePosition != NO_CACHEPOSITION &&
227 this.filePointer >= this.cachePosition &&
228 this.filePointer < this.cachePosition + this.cacheLength )
229 {
230 final long cacheStart = this.filePointer - this.cachePosition;
231
232 assert cacheStart <= Integer.MAX_VALUE :
233 "Unexpected implementation limit reached.";
234
235 final int cachedLength = len > this.cacheLength -
236 (int) cacheStart
237 ? this.cacheLength - (int) cacheStart
238 : len;
239
240 System.arraycopy( buf, off, this.getCache(), (int) cacheStart,
241 cachedLength );
242
243 }
244
245 this.fileOperations.setFilePointer( this.filePointer );
246 this.fileOperations.write( buf, off, len );
247 this.filePointer += len;
248 }
249
250 public void read( final OutputStream out ) throws IOException
251 {
252 this.assertNotClosed();
253
254 this.fileOperations.read( out );
255 this.filePointer = this.fileOperations.getFilePointer();
256 }
257
258 public void write( final InputStream in ) throws IOException
259 {
260 this.assertNotClosed();
261
262 this.fileOperations.write( in );
263 this.filePointer = this.fileOperations.getFilePointer();
264 }
265
266
267
268
269
270
271
272
273
274 public void close() throws IOException
275 {
276 this.assertNotClosed();
277
278 this.flush();
279 this.getFileOperations().close();
280 this.closed = true;
281 }
282
283
284
285
286
287
288
289
290
291 public void flush() throws IOException
292 {
293 this.assertNotClosed();
294
295 if ( this.fileOperations instanceof FlushableFileOperations )
296 {
297 ( (FlushableFileOperations) this.fileOperations ).flush();
298 }
299 }
300
301
302
303
304
305 private final FileOperations fileOperations;
306
307
308 private byte[] cache;
309
310
311 private long cachePosition;
312
313 private static final long NO_CACHEPOSITION = Long.MIN_VALUE;
314
315
316 private int cacheLength;
317
318
319 private long filePointer;
320
321
322 private boolean closed;
323
324
325 private Integer cacheSize;
326
327
328
329
330
331
332
333
334
335
336 public ReadAheadFileOperations( final FileOperations fileOperations )
337 throws IOException
338 {
339 super();
340
341 if ( fileOperations == null )
342 {
343 throw new NullPointerException( "fileOperations" );
344 }
345
346 this.fileOperations = fileOperations;
347 this.filePointer = fileOperations.getFilePointer();
348 }
349
350
351
352
353
354
355
356
357
358
359
360 public ReadAheadFileOperations( final FileOperations fileOperations,
361 final int cacheSize ) throws IOException
362 {
363 this( fileOperations );
364
365 if ( cacheSize > 0 )
366 {
367 this.cacheSize = new Integer( cacheSize );
368 }
369 }
370
371
372
373
374
375
376
377
378 public FileOperations getFileOperations()
379 {
380 return this.fileOperations;
381 }
382
383
384
385
386
387
388 public int getCacheSize()
389 {
390 if ( this.cacheSize == null )
391 {
392 this.cacheSize = this.getDefaultCacheSize();
393 }
394
395 return this.cacheSize.intValue();
396 }
397
398
399
400
401
402
403 private byte[] getCache()
404 {
405 if ( this.cache == null )
406 {
407 this.cache =
408 this.getMemoryManager().allocateBytes( this.getCacheSize() );
409
410 }
411
412 return this.cache;
413 }
414
415
416
417
418
419
420 private void assertNotClosed() throws IOException
421 {
422 if ( this.closed )
423 {
424 throw new IOException( this.getAlreadyClosedMessage(
425 this.getLocale() ) );
426
427 }
428 }
429
430
431
432
433
434
435 private void fillCache() throws IOException
436 {
437 final long delta = this.getLength() - this.filePointer;
438 final int toRead = delta > this.getCache().length
439 ? this.getCache().length
440 : (int) delta;
441
442 this.cachePosition = this.filePointer;
443
444 int totalRead = 0;
445 int readLength = toRead;
446
447 do
448 {
449 this.fileOperations.setFilePointer( this.filePointer );
450 final int read = this.fileOperations.read(
451 this.getCache(), totalRead, readLength );
452
453 assert read != FileOperations.EOF : "Unexpected end of file.";
454
455 totalRead += read;
456 readLength -= read;
457
458 }
459 while ( totalRead < toRead );
460
461 this.cacheLength = toRead;
462 }
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479 private String getAlreadyClosedMessage( final Locale locale )
480 {
481 return ContainerFactory.getContainer().
482 getMessage( this, "alreadyClosed", locale, null );
483
484 }
485
486
487
488
489 }