/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
/*
* @(#)SharedFileInputStream.java 1.11 07/05/04
*/
package javax.mail.util;
import java.io.*;
import javax.mail.internet.SharedInputStream;
/**
* A <code>SharedFileInputStream</code> is a
* <code>BufferedInputStream</code> that buffers
* data from the file and supports the <code>mark</code>
* and <code>reset</code> methods. It also supports the
* <code>newStream</code> method that allows you to create
* other streams that represent subsets of the file.
* A <code>RandomAccessFile</code> object is used to
* access the file data. <p>
*
* Note that when the SharedFileInputStream is closed,
* all streams created with the <code>newStream</code>
* method are also closed. This allows the creator of the
* SharedFileInputStream object to control access to the
* underlying file and ensure that it is closed when
* needed, to avoid leaking file descriptors. Note also
* that this behavior contradicts the requirements of
* SharedInputStream and may change in a future release.
*
* @author Bill Shannon
* @since JavaMail 1.4
*/
public class SharedFileInputStream extends BufferedInputStream
implements SharedInputStream {
private static int defaultBufferSize = 2048;
/**
* The file containing the data.
* Shared by all related SharedFileInputStreams.
*/
protected RandomAccessFile in;
/**
* The normal size of the read buffer.
*/
protected int bufsize;
/**
* The file offset that corresponds to the first byte in
* the read buffer.
*/
protected long bufpos;
/**
* The file offset of the start of data in this subset of the file.
*/
protected long start = 0;
/**
* The amount of data in this subset of the file.
*/
protected long datalen;
/**
* True if this is a top level stream created directly by "new".
* False if this is a derived stream created by newStream.
*/
private boolean master = true;
/**
* A shared class that keeps track of the references
* to a particular file so it can be closed when the
* last reference is gone.
*/
static class SharedFile {
private int cnt;
private RandomAccessFile in;
SharedFile(String file) throws IOException {
this.in = new RandomAccessFile(file, "r");
}
SharedFile(File file) throws IOException {
this.in = new RandomAccessFile(file, "r");
}
public RandomAccessFile open() {
cnt++;
return in;
}
public synchronized void close() throws IOException {
if (cnt > 0 && --cnt <= 0)
in.close();
}
public synchronized void forceClose() throws IOException {
if (cnt > 0) {
// normal case, close exceptions propagated
cnt = 0;
in.close();
} else {
// should already be closed, ignore exception
try {
in.close();
} catch (IOException ioex) { }
}
}
protected void finalize() throws Throwable {
super.finalize();
in.close();
}
}
private SharedFile sf;
/**
* Check to make sure that this stream has not been closed
*/
private void ensureOpen() throws IOException {
if (in == null)
throw new IOException("Stream closed");
}
/**
* Creates a <code>SharedFileInputStream</code>
* for the file.
*
* @param file the file
*/
public SharedFileInputStream(File file) throws IOException {
this(file, defaultBufferSize);
}
/**
* Creates a <code>SharedFileInputStream</code>
* for the named file
*
* @param file the file
*/
public SharedFileInputStream(String file) throws IOException {
this(file, defaultBufferSize);
}
/**
* Creates a <code>SharedFileInputStream</code>
* with the specified buffer size.
*
* @param file the file
* @param size the buffer size.
* @exception IllegalArgumentException if size <= 0.
*/
public SharedFileInputStream(File file, int size) throws IOException {
super(null); // XXX - will it NPE?
if (size <= 0)
throw new IllegalArgumentException("Buffer size <= 0");
init(new SharedFile(file), size);
}
/**
* Creates a <code>SharedFileInputStream</code>
* with the specified buffer size.
*
* @param file the file
* @param size the buffer size.
* @exception IllegalArgumentException if size <= 0.
*/
public SharedFileInputStream(String file, int size) throws IOException {
super(null); // XXX - will it NPE?
if (size <= 0)
throw new IllegalArgumentException("Buffer size <= 0");
init(new SharedFile(file), size);
}
private void init(SharedFile sf, int size) throws IOException {
this.sf = sf;
this.in = sf.open();
this.start = 0;
this.datalen = in.length(); // XXX - file can't grow
this.bufsize = size;
buf = new byte[size];
}
/**
* Used internally by the <code>newStream</code> method.
*/
private SharedFileInputStream(SharedFile sf, long start, long len,
int bufsize) {
super(null);
this.master = false;
this.sf = sf;
this.in = sf.open();
this.start = start;
this.bufpos = start;
this.datalen = len;
this.bufsize = bufsize;
buf = new byte[bufsize];
}
/**
* Fills the buffer with more data, taking into account
* shuffling and other tricks for dealing with marks.
* Assumes that it is being called by a synchronized method.
* This method also assumes that all data has already been read in,
* hence pos > count.
*/
private void fill() throws IOException {
if (markpos < 0) {
pos = 0; /* no mark: throw away the buffer */
bufpos += count;
} else if (pos >= buf.length) /* no room left in buffer */
if (markpos > 0) { /* can throw away early part of the buffer */
int sz = pos - markpos;
System.arraycopy(buf, markpos, buf, 0, sz);
pos = sz;
bufpos += markpos;
markpos = 0;
} else if (buf.length >= marklimit) {
markpos = -1; /* buffer got too big, invalidate mark */
pos = 0; /* drop buffer contents */
bufpos += count;
} else { /* grow buffer */
int nsz = pos * 2;
if (nsz > marklimit)
nsz = marklimit;
byte nbuf[] = new byte[nsz];
System.arraycopy(buf, 0, nbuf, 0, pos);
buf = nbuf;
}
count = pos;
in.seek(bufpos + pos);
// limit to datalen
int len = buf.length - pos;
if (bufpos - start + pos + len > datalen)
len = (int)(datalen - (bufpos - start + pos));
int n = in.read(buf, pos, len);
if (n > 0)
count = n + pos;
}
/**
* See the general contract of the <code>read</code>
* method of <code>InputStream</code>.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream is reached.
* @exception IOException if an I/O error occurs.
*/
public synchronized int read() throws IOException {
ensureOpen();
if (pos >= count) {
fill();
if (pos >= count)
return -1;
}
return buf[pos++] & 0xff;
}
/**
* Read characters into a portion of an array, reading from the underlying
* stream at most once if necessary.
*/
private int read1(byte[] b, int off, int len) throws IOException {
int avail = count - pos;
if (avail <= 0) {
if (false) {
/* If the requested length is at least as large as the buffer, and
if there is no mark/reset activity, do not bother to copy the
bytes into the local buffer. In this way buffered streams will
cascade harmlessly. */
if (len >= buf.length && markpos < 0) {
// XXX - seek, update bufpos - how?
return in.read(b, off, len);
}
}
fill();
avail = count - pos;
if (avail <= 0) return -1;
}
int cnt = (avail < len) ? avail : len;
System.arraycopy(buf, pos, b, off, cnt);
pos += cnt;
return cnt;
}
/**
* Reads bytes from this stream into the specified byte array,
* starting at the given offset.
*
* <p> This method implements the general contract of the corresponding
* <code>{@link java.io.InputStream#read(byte[], int, int) read}</code>
* method of the <code>{@link java.io.InputStream}</code> class.
*
* @param b destination buffer.
* @param off offset at which to start storing bytes.
* @param len maximum number of bytes to read.
* @return the number of bytes read, or <code>-1</code> if the end of
* the stream has been reached.
* @exception IOException if an I/O error occurs.
*/
public synchronized int read(byte b[], int off, int len)
throws IOException
{
ensureOpen();
if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int n = read1(b, off, len);
if (n <= 0) return n;
while ((n < len) /* && (in.available() > 0) */) {
int n1 = read1(b, off + n, len - n);
if (n1 <= 0) break;
n += n1;
}
return n;
}
/**
* See the general contract of the <code>skip</code>
* method of <code>InputStream</code>.
*
* @param n the number of bytes to be skipped.
* @return the actual number of bytes skipped.
* @exception IOException if an I/O error occurs.
*/
public synchronized long skip(long n) throws IOException {
ensureOpen();
if (n <= 0) {
return 0;
}
long avail = count - pos;
if (avail <= 0) {
// If no mark position set then don't keep in buffer
/*
if (markpos <0)
return in.skip(n);
*/
// Fill in buffer to save bytes for reset
fill();
avail = count - pos;
if (avail <= 0)
return 0;
}
long skipped = (avail < n) ? avail : n;
pos += skipped;
return skipped;
}
/**
* Returns the number of bytes that can be read from this input
* stream without blocking.
*
* @return the number of bytes that can be read from this input
* stream without blocking.
* @exception IOException if an I/O error occurs.
*/
public synchronized int available() throws IOException {
ensureOpen();
return (count - pos) + in_available();
}
private int in_available() throws IOException {
// XXX - overflow
return (int)((start + datalen) - (bufpos + count));
}
/**
* See the general contract of the <code>mark</code>
* method of <code>InputStream</code>.
*
* @param readlimit the maximum limit of bytes that can be read before
* the mark position becomes invalid.
* @see #reset()
*/
public synchronized void mark(int readlimit) {
marklimit = readlimit;
markpos = pos;
}
/**
* See the general contract of the <code>reset</code>
* method of <code>InputStream</code>.
* <p>
* If <code>markpos</code> is <code>-1</code>
* (no mark has been set or the mark has been
* invalidated), an <code>IOException</code>
* is thrown. Otherwise, <code>pos</code> is
* set equal to <code>markpos</code>.
*
* @exception IOException if this stream has not been marked or
* if the mark has been invalidated.
* @see #mark(int)
*/
public synchronized void reset() throws IOException {
ensureOpen();
if (markpos < 0)
throw new IOException("Resetting to invalid mark");
pos = markpos;
}
/**
* Tests if this input stream supports the <code>mark</code>
* and <code>reset</code> methods. The <code>markSupported</code>
* method of <code>SharedFileInputStream</code> returns
* <code>true</code>.
*
* @return a <code>boolean</code> indicating if this stream type supports
* the <code>mark</code> and <code>reset</code> methods.
* @see java.io.InputStream#mark(int)
* @see java.io.InputStream#reset()
*/
public boolean markSupported() {
return true;
}
/**
* Closes this input stream and releases any system resources
* associated with the stream.
*
* @exception IOException if an I/O error occurs.
*/
public void close() throws IOException {
if (in == null)
return;
try {
if (master)
sf.forceClose();
else
sf.close();
} finally {
sf = null;
in = null;
buf = null;
}
}
/**
* Return the current position in the InputStream, as an
* offset from the beginning of the InputStream.
*
* @return the current position
*/
public long getPosition() {
//System.out.println("getPosition: start " + start + " pos " + pos + " bufpos " + bufpos + " = " + (bufpos + pos - start));
if (in == null)
throw new RuntimeException("Stream closed");
return bufpos + pos - start;
}
/**
* Return a new InputStream representing a subset of the data
* from this InputStream, starting at <code>start</code> (inclusive)
* up to <code>end</code> (exclusive). <code>start</code> must be
* non-negative. If <code>end</code> is -1, the new stream ends
* at the same place as this stream. The returned InputStream
* will also implement the SharedInputStream interface.
*
* @param start the starting position
* @param end the ending position + 1
* @return the new stream
*/
public InputStream newStream(long start, long end) {
if (in == null)
throw new RuntimeException("Stream closed");
if (start < 0)
throw new IllegalArgumentException("start < 0");
if (end == -1)
end = datalen;
return new SharedFileInputStream(sf,
this.start + (int)start, (int)(end - start), bufsize);
}
// for testing...
/*
public static void main(String[] argv) throws Exception {
SharedFileInputStream is = new SharedFileInputStream(argv[0]);
java.util.Random r = new java.util.Random();
int b;
while ((b = is.read()) >= 0) {
System.out.write(b);
if (r.nextDouble() < 0.3) {
InputStream is2 = is.newStream(is.getPosition(), -1);
int b2;
while ((b2 = is2.read()) >= 0)
;
}
}
}
*/
/**
* Force this stream to close.
*/
protected void finalize() throws Throwable {
super.finalize();
close();
}
}