1 /**
2 *
3 * Copyright 2006 The Apache Software Foundation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package javax.mail.util;
19
20 import java.io.BufferedInputStream;
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.RandomAccessFile;
25
26 import javax.mail.internet.SharedInputStream;
27
28 public class SharedFileInputStream extends BufferedInputStream implements SharedInputStream {
29
30
31
32
33 private static final int DEFAULT_BUFFER_SIZE = 2048;
34
35
36 private SharedFileSource source;
37
38 /**
39 * The file offset that is the first byte in the read buffer.
40 */
41 protected long bufpos;
42
43 /**
44 * The normal size of the read buffer.
45 */
46 protected int bufsize;
47
48 /**
49 * The size of the file subset represented by this stream instance.
50 */
51 protected long datalen;
52
53 /**
54 * The source of the file data. This is shared across multiple
55 * instances.
56 */
57 protected RandomAccessFile in;
58
59 /**
60 * The starting position of data represented by this stream relative
61 * to the start of the file data. This stream instance represents
62 * data in the range start to (start + datalen - 1).
63 */
64 protected long start;
65
66
67 /**
68 * Construct a SharedFileInputStream from a file name, using the default buffer size.
69 *
70 * @param file The name of the file.
71 *
72 * @exception IOException
73 */
74 public SharedFileInputStream(String file) throws IOException {
75 this(file, DEFAULT_BUFFER_SIZE);
76 }
77
78
79 /**
80 * Construct a SharedFileInputStream from a File object, using the default buffer size.
81 *
82 * @param file The name of the file.
83 *
84 * @exception IOException
85 */
86 public SharedFileInputStream(File file) throws IOException {
87 this(file, DEFAULT_BUFFER_SIZE);
88 }
89
90
91 /**
92 * Construct a SharedFileInputStream from a file name, with a given initial buffer size.
93 *
94 * @param file The name of the file.
95 * @param bufferSize The initial buffer size.
96 *
97 * @exception IOException
98 */
99 public SharedFileInputStream(String file, int bufferSize) throws IOException {
100
101
102
103 super(null);
104 init(new File(file), bufferSize);
105 }
106
107
108 /**
109 * Construct a SharedFileInputStream from a File object, with a given initial buffer size.
110 *
111 * @param file The name of the file.
112 * @param bufferSize The initial buffer size.
113 *
114 * @exception IOException
115 */
116 public SharedFileInputStream(File file, int bufferSize) throws IOException {
117
118
119
120 super(null);
121 init(file, bufferSize);
122 }
123
124
125 /**
126 * Private constructor used to spawn off a shared instance
127 * of this stream.
128 *
129 * @param source The internal class object that manages the shared resources of
130 * the stream.
131 * @param start The starting offset relative to the beginning of the file.
132 * @param len The length of file data in this shared instance.
133 * @param bufsize The initial buffer size (same as the spawning parent.
134 */
135 private SharedFileInputStream(SharedFileSource source, long start, long len, int bufsize) {
136 super(null);
137 this.source = source;
138 in = source.open();
139 this.start = start;
140 bufpos = start;
141 datalen = len;
142 this.bufsize = bufsize;
143 buf = new byte[bufsize];
144
145 }
146
147
148 /**
149 * Shared initializtion routine for the constructors.
150 *
151 * @param file The file we're accessing.
152 * @param bufferSize The initial buffer size to use.
153 *
154 * @exception IOException
155 */
156 private void init(File file, int bufferSize) throws IOException {
157 if (bufferSize <= 0) {
158 throw new IllegalArgumentException("Buffer size must be positive");
159 }
160
161
162 source = new SharedFileSource(file);
163
164 in = source.open();
165
166 start = 0;
167
168 datalen = in.length();
169
170 bufsize = bufferSize;
171 bufpos = 0;
172
173 buf = new byte[bufferSize];
174 }
175
176
177 /**
178 * Check to see if we need to read more data into our buffer.
179 *
180 * @return False if there's not valid data in the buffer (generally means
181 * an EOF condition).
182 * @exception IOException
183 */
184 private boolean checkFill() throws IOException {
185
186 if (pos < count) {
187 return true;
188 }
189
190
191
192 if (markpos < 0) {
193
194 pos = 0;
195
196 bufpos += count;
197 }
198 else {
199
200
201
202
203
204 if (pos >= buf.length) {
205
206
207 if (markpos > 0) {
208
209 int validSize = pos - markpos;
210
211 System.arraycopy(buf, markpos, buf, 0, validSize);
212
213 pos = validSize;
214 bufpos += markpos;
215 markpos = 0;
216 }
217
218
219 else if (buf.length < marklimit) {
220
221 int newSize = Math.min(buf.length * 2, marklimit);
222
223 byte[] newBuffer = new byte[newSize];
224 System.arraycopy(buf, 0, newBuffer, 0, buf.length);
225
226
227 buf = newBuffer;
228 }
229
230 else {
231 markpos = -1;
232 pos = 0;
233 bufpos += count;
234 }
235 }
236 }
237
238
239 if (bufpos + pos >= start + datalen) {
240 return false;
241 }
242
243
244
245 int fillLength = buf.length - pos;
246
247
248
249 if (bufpos - start + pos + fillLength > datalen) {
250 fillLength = (int)(datalen - (bufpos - start + pos));
251 }
252
253
254 fillLength = source.read(bufpos + pos, buf, pos, fillLength);
255
256
257 if (fillLength <= 0) {
258 return false;
259 }
260
261
262 count = fillLength + pos;
263
264
265 return true;
266 }
267
268
269 /**
270 * Return the number of bytes available for reading without
271 * blocking for a long period.
272 *
273 * @return For this stream, this is the number of bytes between the
274 * current read position and the indicated end of the file.
275 * @exception IOException
276 */
277 public synchronized int available() throws IOException {
278 checkOpen();
279
280
281
282 long endMarker = start + datalen;
283 return (int)(endMarker - (bufpos + pos));
284 }
285
286
287 /**
288 * Return the current read position of the stream.
289 *
290 * @return The current position relative to the beginning of the stream.
291 * This is not the position relative to the start of the file, since
292 * the stream starting position may be other than the beginning.
293 */
294 public long getPosition() {
295 checkOpenRuntime();
296
297 return bufpos + pos - start;
298 }
299
300
301 /**
302 * Mark the current position for retracing.
303 *
304 * @param readlimit The limit for the distance the read position can move from
305 * the mark position before the mark is reset.
306 */
307 public synchronized void mark(int readlimit) {
308 checkOpenRuntime();
309 marklimit = readlimit;
310 markpos = pos;
311 }
312
313
314 /**
315 * Read a single byte of data from the input stream.
316 *
317 * @return The read byte. Returns -1 if an eof condition has been hit.
318 * @exception IOException
319 */
320 public synchronized int read() throws IOException {
321 checkOpen();
322
323
324 if (!checkFill()) {
325 return -1;
326 }
327
328
329 return buf[pos++] & 0xff;
330 }
331
332
333 /**
334 * Read multiple bytes of data and place them directly into
335 * a byte-array buffer.
336 *
337 * @param buffer The target buffer.
338 * @param offset The offset within the buffer to place the data.
339 * @param length The length to attempt to read.
340 *
341 * @return The number of bytes actually read. Returns -1 for an EOF
342 * condition.
343 * @exception IOException
344 */
345 public synchronized int read(byte buffer[], int offset, int length) throws IOException {
346 checkOpen();
347
348
349 if (length == 0) {
350 return 0;
351 }
352
353
354 int returnCount = 0;
355 while (length > 0) {
356
357 if (!checkFill()) {
358
359 if (returnCount > 0) {
360 return returnCount;
361 }
362
363 return -1;
364 }
365
366 int available = count - pos;
367 int given = Math.min(available, length);
368
369 System.arraycopy(buf, pos, buffer, offset, given);
370
371
372 pos += given;
373 length -= given;
374 returnCount += given;
375 offset += given;
376 }
377
378 return returnCount;
379 }
380
381
382 /**
383 * Skip the read pointer ahead a given number of bytes.
384 *
385 * @param n The number of bytes to skip.
386 *
387 * @return The number of bytes actually skipped.
388 * @exception IOException
389 */
390 public synchronized long skip(long n) throws IOException {
391 checkOpen();
392
393
394 if (n <= 0) {
395 return 0;
396 }
397
398
399 if (!checkFill()) {
400 return 0;
401 }
402
403 long available = count - pos;
404
405
406 long skipped = available < n ? available : n;
407 pos += skipped;
408 return skipped;
409 }
410
411 /**
412 * Reset the mark position.
413 *
414 * @exception IOException
415 */
416 public synchronized void reset() throws IOException {
417 checkOpen();
418 if (markpos < 0) {
419 throw new IOException("Resetting to invalid mark position");
420 }
421
422 pos = markpos;
423 }
424
425
426 /**
427 * Indicates the mark() operation is supported.
428 *
429 * @return Always returns true.
430 */
431 public boolean markSupported() {
432 return true;
433 }
434
435
436 /**
437 * Close the stream. This does not close the source file until
438 * the last shared instance is closed.
439 *
440 * @exception IOException
441 */
442 public void close() throws IOException {
443
444 if (in == null) {
445 return;
446 }
447
448 try {
449
450 source.close();
451 } finally {
452 in = null;
453 }
454 }
455
456
457 /**
458 * Create a new stream from this stream, using the given
459 * start offset and length.
460 *
461 * @param offset The offset relative to the start of this stream instance.
462 * @param end The end offset of the substream. If -1, the end of the parent stream is used.
463 *
464 * @return A new SharedFileInputStream object sharing the same source
465 * input file.
466 */
467 public InputStream newStream(long offset, long end) {
468 checkOpenRuntime();
469
470 if (offset < 0) {
471 throw new IllegalArgumentException("Start position is less than 0");
472 }
473
474 if (end == -1) {
475 end = datalen;
476 }
477
478
479 return new SharedFileInputStream(source, start + (int)offset, (int)(end - offset), bufsize);
480 }
481
482
483 /**
484 * Check if the file is open and throw an IOException if not.
485 *
486 * @exception IOException
487 */
488 private void checkOpen() throws IOException {
489 if (in == null) {
490 throw new IOException("Stream has been closed");
491 }
492 }
493
494
495 /**
496 * Check if the file is open and throw an IOException if not. This version is
497 * used because several API methods are not defined as throwing IOException, so
498 * checkOpen() can't be used. The Sun implementation just throws RuntimeExceptions
499 * in those methods, hence 2 versions.
500 *
501 * @exception RuntimeException
502 */
503 private void checkOpenRuntime() {
504 if (in == null) {
505 throw new RuntimeException("Stream has been closed");
506 }
507 }
508
509
510 /**
511 * Internal class used to manage resources shared between the
512 * ShareFileInputStream instances.
513 */
514 class SharedFileSource {
515
516 public RandomAccessFile source;
517
518 public int instanceCount = 0;
519
520 public SharedFileSource(File file) throws IOException {
521 source = new RandomAccessFile(file, "r");
522 }
523
524 /**
525 * Open the shared stream to keep track of open instances.
526 */
527 public synchronized RandomAccessFile open() {
528 instanceCount++;
529 return source;
530 }
531
532 /**
533 * Process a close request for this stream. If there are multiple
534 * instances using this underlying stream, the stream will not
535 * be closed.
536 *
537 * @exception IOException
538 */
539 public synchronized void close() throws IOException {
540 if (instanceCount > 0) {
541 instanceCount--;
542
543 if (instanceCount == 0) {
544 source.close();
545 }
546 }
547 }
548
549 /**
550 * Read a buffer of data from the shared file.
551 *
552 * @param position The position to read from.
553 * @param buf The target buffer for storing the read data.
554 * @param offset The starting offset within the buffer.
555 * @param length The length to attempt to read.
556 *
557 * @return The number of bytes actually read.
558 * @exception IOException
559 */
560 public synchronized int read(long position, byte[] buf, int offset, int length) throws IOException {
561
562
563 source.seek(position);
564 return source.read(buf, offset, length);
565 }
566
567
568 /**
569 * Ensure the stream is closed when this shared object is finalized.
570 *
571 * @exception Throwable
572 */
573 protected void finalize() throws Throwable {
574 super.finalize();
575 if (instanceCount > 0) {
576 source.close();
577 }
578 }
579 }
580 }
581