Skip to contentMethod: in_available()
1: /*
2: * Copyright (c) 1997, 2021 Oracle and/or its affiliates. All rights reserved.
3: *
4: * This program and the accompanying materials are made available under the
5: * terms of the Eclipse Public License v. 2.0, which is available at
6: * http://www.eclipse.org/legal/epl-2.0.
7: *
8: * This Source Code may also be made available under the following Secondary
9: * Licenses when the conditions for such availability set forth in the
10: * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11: * version 2 with the GNU Classpath Exception, which is available at
12: * https://www.gnu.org/software/classpath/license.html.
13: *
14: * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15: */
16:
17: package jakarta.mail.util;
18:
19: import java.io.*;
20:
21: import jakarta.mail.internet.SharedInputStream;
22:
23: /**
24: * A <code>SharedFileInputStream</code> is a
25: * <code>BufferedInputStream</code> that buffers
26: * data from the file and supports the <code>mark</code>
27: * and <code>reset</code> methods. It also supports the
28: * <code>newStream</code> method that allows you to create
29: * other streams that represent subsets of the file.
30: * A <code>RandomAccessFile</code> object is used to
31: * access the file data. <p>
32: *
33: * Note that when the SharedFileInputStream is closed,
34: * all streams created with the <code>newStream</code>
35: * method are also closed. This allows the creator of the
36: * SharedFileInputStream object to control access to the
37: * underlying file and ensure that it is closed when
38: * needed, to avoid leaking file descriptors. Note also
39: * that this behavior contradicts the requirements of
40: * SharedInputStream and may change in a future release.
41: *
42: * @author Bill Shannon
43: * @since JavaMail 1.4
44: */
45: public class SharedFileInputStream extends BufferedInputStream
46:                                 implements SharedInputStream {
47:
48: private static int defaultBufferSize = 2048;
49:
50: /**
51: * The file containing the data.
52: * Shared by all related SharedFileInputStreams.
53: */
54: protected RandomAccessFile in;
55:
56: /**
57: * The normal size of the read buffer.
58: */
59: protected int bufsize;
60:
61: /**
62: * The file offset that corresponds to the first byte in
63: * the read buffer.
64: */
65: protected long bufpos;
66:
67: /**
68: * The file offset of the start of data in this subset of the file.
69: */
70: protected long start = 0;
71:
72: /**
73: * The amount of data in this subset of the file.
74: */
75: protected long datalen;
76:
77: /**
78: * True if this is a top level stream created directly by "new".
79: * False if this is a derived stream created by newStream.
80: */
81: private boolean master = true;
82:
83: /**
84: * A shared class that keeps track of the references
85: * to a particular file so it can be closed when the
86: * last reference is gone.
87: */
88: static class SharedFile {
89:         private int cnt;
90:         private RandomAccessFile in;
91:
92:         SharedFile(String file) throws IOException {
93:          this.in = new RandomAccessFile(file, "r");
94:         }
95:
96:         SharedFile(File file) throws IOException {
97:          this.in = new RandomAccessFile(file, "r");
98:         }
99:
100:         public synchronized RandomAccessFile open() {
101:          cnt++;
102:          return in;
103:         }
104:
105:         public synchronized void close() throws IOException {
106:          if (cnt > 0 && --cnt <= 0)
107:                 in.close();
108:         }
109:
110:         public synchronized void forceClose() throws IOException {
111:          if (cnt > 0) {
112:                 // normal case, close exceptions propagated
113:                 cnt = 0;
114:                 in.close();
115:          } else {
116:                 // should already be closed, ignore exception
117:                 try {
118:                  in.close();
119:                 } catch (IOException ioex) { }
120:          }
121:         }
122:
123:         @Override
124:         protected void finalize() throws Throwable {
125:          try {
126:                 in.close();
127:          } finally {
128:                 super.finalize();
129:          }
130:         }
131: }
132:
133: private SharedFile sf;
134:
135: /**
136: * Check to make sure that this stream has not been closed
137: */
138: private void ensureOpen() throws IOException {
139:         if (in == null)
140:          throw new IOException("Stream closed");
141: }
142:
143: /**
144: * Creates a <code>SharedFileInputStream</code>
145: * for the file.
146: *
147: * @param file the file
148: * @exception IOException for errors opening the file
149: */
150: public SharedFileInputStream(File file) throws IOException {
151:         this(file, defaultBufferSize);
152: }
153:
154: /**
155: * Creates a <code>SharedFileInputStream</code>
156: * for the named file
157: *
158: * @param file the file
159: * @exception IOException for errors opening the file
160: */
161: public SharedFileInputStream(String file) throws IOException {
162:         this(file, defaultBufferSize);
163: }
164:
165: /**
166: * Creates a <code>SharedFileInputStream</code>
167: * with the specified buffer size.
168: *
169: * @param file        the file
170: * @param size the buffer size.
171: * @exception IOException for errors opening the file
172: * @exception IllegalArgumentException if size ≤ 0.
173: */
174: public SharedFileInputStream(File file, int size) throws IOException {
175:         super(null);        // XXX - will it NPE?
176: if (size <= 0)
177: throw new IllegalArgumentException("Buffer size <= 0");
178:         init(new SharedFile(file), size);
179: }
180:
181: /**
182: * Creates a <code>SharedFileInputStream</code>
183: * with the specified buffer size.
184: *
185: * @param file        the file
186: * @param size the buffer size.
187: * @exception IOException for errors opening the file
188: * @exception IllegalArgumentException if size ≤ 0.
189: */
190: public SharedFileInputStream(String file, int size) throws IOException {
191:         super(null);        // XXX - will it NPE?
192: if (size <= 0)
193: throw new IllegalArgumentException("Buffer size <= 0");
194:         init(new SharedFile(file), size);
195: }
196:
197: private void init(SharedFile sf, int size) throws IOException {
198:         this.sf = sf;
199:         this.in = sf.open();
200:         this.start = 0;
201:         this.datalen = in.length();        // XXX - file can't grow
202:         this.bufsize = size;
203:         buf = new byte[size];
204: }
205:
206: /**
207: * Used internally by the <code>newStream</code> method.
208: */
209: private SharedFileInputStream(SharedFile sf, long start, long len,
210:                                 int bufsize) {
211:         super(null);
212:         this.master = false;
213:         this.sf = sf;
214:         this.in = sf.open();
215:         this.start = start;
216:         this.bufpos = start;
217:         this.datalen = len;
218:         this.bufsize = bufsize;
219:         buf = new byte[bufsize];
220: }
221:
222: /**
223: * Fills the buffer with more data, taking into account
224: * shuffling and other tricks for dealing with marks.
225: * Assumes that it is being called by a synchronized method.
226: * This method also assumes that all data has already been read in,
227: * hence pos > count.
228: */
229: private void fill() throws IOException {
230:         if (markpos < 0) {
231:          pos = 0;                /* no mark: throw away the buffer */
232:          bufpos += count;
233:         } else if (pos >= buf.length)        /* no room left in buffer */
234:          if (markpos > 0) {        /* can throw away early part of the buffer */
235:                 int sz = pos - markpos;
236:                 System.arraycopy(buf, markpos, buf, 0, sz);
237:                 pos = sz;
238:                 bufpos += markpos;
239:                 markpos = 0;
240:          } else if (buf.length >= marklimit) {
241:                 markpos = -1;        /* buffer got too big, invalidate mark */
242:                 pos = 0;        /* drop buffer contents */
243:                 bufpos += count;
244:          } else {                /* grow buffer */
245:                 int nsz = pos * 2;
246:                 if (nsz > marklimit)
247:                  nsz = marklimit;
248:                 byte nbuf[] = new byte[nsz];
249:                 System.arraycopy(buf, 0, nbuf, 0, pos);
250:                 buf = nbuf;
251:          }
252: count = pos;
253:         // limit to datalen
254:         int len = buf.length - pos;
255:         if (bufpos - start + pos + len > datalen)
256:          len = (int)(datalen - (bufpos - start + pos));
257:         synchronized (in) {
258:          in.seek(bufpos + pos);
259:          int n = in.read(buf, pos, len);
260:          if (n > 0)
261:                 count = n + pos;
262:         }
263: }
264:
265: /**
266: * See the general contract of the <code>read</code>
267: * method of <code>InputStream</code>.
268: *
269: * @return the next byte of data, or <code>-1</code> if the end of the
270: * stream is reached.
271: * @exception IOException if an I/O error occurs.
272: */
273: @Override
274: public synchronized int read() throws IOException {
275: ensureOpen();
276:         if (pos >= count) {
277:          fill();
278:          if (pos >= count)
279:                 return -1;
280:         }
281:         return buf[pos++] & 0xff;
282: }
283:
284: /**
285: * Read characters into a portion of an array, reading from the underlying
286: * stream at most once if necessary.
287: */
288: private int read1(byte[] b, int off, int len) throws IOException {
289:         int avail = count - pos;
290:         if (avail <= 0) {
291:          if (false) {
292:          /* If the requested length is at least as large as the buffer, and
293:          if there is no mark/reset activity, do not bother to copy the
294:          bytes into the local buffer. In this way buffered streams will
295:          cascade harmlessly. */
296:          if (len >= buf.length && markpos < 0) {
297:                 // XXX - seek, update bufpos - how?
298:                 return in.read(b, off, len);
299:          }
300:          }
301:          fill();
302:          avail = count - pos;
303:          if (avail <= 0) return -1;
304:         }
305:         int cnt = (avail < len) ? avail : len;
306:         System.arraycopy(buf, pos, b, off, cnt);
307:         pos += cnt;
308:         return cnt;
309: }
310:
311: /**
312: * Reads bytes from this stream into the specified byte array,
313: * starting at the given offset.
314: *
315: * <p> This method implements the general contract of the corresponding
316: * <code>{@link java.io.InputStream#read(byte[], int, int) read}</code>
317: * method of the <code>{@link java.io.InputStream}</code> class.
318: *
319: * @param b destination buffer.
320: * @param off offset at which to start storing bytes.
321: * @param len maximum number of bytes to read.
322: * @return the number of bytes read, or <code>-1</code> if the end of
323: * the stream has been reached.
324: * @exception IOException if an I/O error occurs.
325: */
326: @Override
327: public synchronized int read(byte b[], int off, int len)
328:         throws IOException
329: {
330: ensureOpen();
331: if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
332:          throw new IndexOutOfBoundsException();
333:         } else if (len == 0) {
334:          return 0;
335:         }
336:
337:         int n = read1(b, off, len);
338:         if (n <= 0) return n;
339:         while ((n < len) /* && (in.available() > 0) */) {
340:          int n1 = read1(b, off + n, len - n);
341:          if (n1 <= 0) break;
342:          n += n1;
343:         }
344:         return n;
345: }
346:
347: /**
348: * See the general contract of the <code>skip</code>
349: * method of <code>InputStream</code>.
350: *
351: * @param n the number of bytes to be skipped.
352: * @return the actual number of bytes skipped.
353: * @exception IOException if an I/O error occurs.
354: */
355: @Override
356: public synchronized long skip(long n) throws IOException {
357: ensureOpen();
358:         if (n <= 0) {
359:          return 0;
360:         }
361:         long avail = count - pos;
362:
363: if (avail <= 0) {
364: // If no mark position set then don't keep in buffer
365:          /*
366: if (markpos <0)
367: return in.skip(n);
368:          */
369:
370: // Fill in buffer to save bytes for reset
371: fill();
372: avail = count - pos;
373: if (avail <= 0)
374: return 0;
375: }
376:
377: long skipped = (avail < n) ? avail : n;
378: pos += skipped;
379: return skipped;
380: }
381:
382: /**
383: * Returns the number of bytes that can be read from this input
384: * stream without blocking.
385: *
386: * @return the number of bytes that can be read from this input
387: * stream without blocking.
388: * @exception IOException if an I/O error occurs.
389: */
390: @Override
391: public synchronized int available() throws IOException {
392: ensureOpen();
393:         return (count - pos) + in_available();
394: }
395:
396: private int in_available() throws IOException {
397:         // XXX - overflow
398:         return (int)((start + datalen) - (bufpos + count));
399: }
400:
401: /**
402: * See the general contract of the <code>mark</code>
403: * method of <code>InputStream</code>.
404: *
405: * @param readlimit the maximum limit of bytes that can be read before
406: * the mark position becomes invalid.
407: * @see #reset()
408: */
409: @Override
410: public synchronized void mark(int readlimit) {
411:         marklimit = readlimit;
412:         markpos = pos;
413: }
414:
415: /**
416: * See the general contract of the <code>reset</code>
417: * method of <code>InputStream</code>.
418: * <p>
419: * If <code>markpos</code> is <code>-1</code>
420: * (no mark has been set or the mark has been
421: * invalidated), an <code>IOException</code>
422: * is thrown. Otherwise, <code>pos</code> is
423: * set equal to <code>markpos</code>.
424: *
425: * @exception IOException if this stream has not been marked or
426: * if the mark has been invalidated.
427: * @see #mark(int)
428: */
429: @Override
430: public synchronized void reset() throws IOException {
431: ensureOpen();
432:         if (markpos < 0)
433:          throw new IOException("Resetting to invalid mark");
434:         pos = markpos;
435: }
436:
437: /**
438: * Tests if this input stream supports the <code>mark</code>
439: * and <code>reset</code> methods. The <code>markSupported</code>
440: * method of <code>SharedFileInputStream</code> returns
441: * <code>true</code>.
442: *
443: * @return a <code>boolean</code> indicating if this stream type supports
444: * the <code>mark</code> and <code>reset</code> methods.
445: * @see java.io.InputStream#mark(int)
446: * @see java.io.InputStream#reset()
447: */
448: @Override
449: public boolean markSupported() {
450:         return true;
451: }
452:
453: /**
454: * Closes this input stream and releases any system resources
455: * associated with the stream.
456: *
457: * @exception IOException if an I/O error occurs.
458: */
459: @Override
460: public void close() throws IOException {
461: if (in == null)
462: return;
463:         try {
464:          if (master)
465:                 sf.forceClose();
466:          else
467:                 sf.close();
468:         } finally {
469:          sf = null;
470:          in = null;
471:          buf = null;
472:         }
473: }
474:
475: /**
476: * Return the current position in the InputStream, as an
477: * offset from the beginning of the InputStream.
478: *
479: * @return the current position
480: */
481: @Override
482: public long getPosition() {
483: //System.out.println("getPosition: start " + start + " pos " + pos
484: //        + " bufpos " + bufpos + " = " + (bufpos + pos - start));
485:         if (in == null)
486:          throw new RuntimeException("Stream closed");
487:         return bufpos + pos - start;
488: }
489:
490: /**
491: * Return a new InputStream representing a subset of the data
492: * from this InputStream, starting at <code>start</code> (inclusive)
493: * up to <code>end</code> (exclusive). <code>start</code> must be
494: * non-negative. If <code>end</code> is -1, the new stream ends
495: * at the same place as this stream. The returned InputStream
496: * will also implement the SharedInputStream interface.
497: *
498: * @param        start        the starting position
499: * @param        end        the ending position + 1
500: * @return                the new stream
501: */
502: @Override
503: public synchronized InputStream newStream(long start, long end) {
504:         if (in == null)
505:          throw new RuntimeException("Stream closed");
506:         if (start < 0)
507:          throw new IllegalArgumentException("start < 0");
508:         if (end == -1)
509:          end = datalen;
510:         return new SharedFileInputStream(sf,
511:                         this.start + start, end - start, bufsize);
512: }
513:
514: // for testing...
515: /*
516: public static void main(String[] argv) throws Exception {
517:         SharedFileInputStream is = new SharedFileInputStream(argv[0]);
518:         java.util.Random r = new java.util.Random();
519:         int b;
520:         while ((b = is.read()) >= 0) {
521:          System.out.write(b);
522:          if (r.nextDouble() < 0.3) {
523:                 InputStream is2 = is.newStream(is.getPosition(), -1);
524:                 int b2;
525:                 while ((b2 = is2.read()) >= 0)
526:                  ;
527:          }
528:         }
529: }
530: */
531:
532: /**
533: * Force this stream to close.
534: */
535: @Override
536: protected void finalize() throws Throwable {
537:         super.finalize();
538:         close();
539: }
540: }