/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.vfs; import com.caucho.util.Alarm; import com.caucho.util.CharBuffer; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.util.Iterator; import java.util.logging.Level; import java.util.logging.Logger; /** * A fast bufferered input stream supporting both character * and byte data. The underlying stream sources are provided by StreamImpl * classes, so all streams have the same API regardless of the underlying * implementation. * * <p>Dynamic streams, like tcp and http * will properly flush writes before reading input. And random access * streams, like RandomAccessFile, can use the same API as normal streams. * * <p>Most applications will use the Path routines to create their own streams. * Specialized applications, like servers, need the capability of recycling * streams. */ public final class ReadStream extends InputStream implements LockableStream { public static int ZERO_COPY_SIZE = 1024; public static final int READ_TIMEOUT = -4; private TempBuffer _tempRead; private byte []_readBuffer; private int _readOffset; private int _readLength; private WriteStream _sibling; private StreamImpl _source; private long _position; private boolean _isEnableReadTime; private long _readTime; private Reader _readEncoding; private String _readEncodingName; private CharBuffer _cb; private boolean _disableClose; private boolean _isDisableCloseSource; private boolean _reuseBuffer; private Reader _reader; /** * Creates an uninitialized stream. Use <code>init</code> to initialize. */ public ReadStream() { } /** * Creates a stream and initializes with a specified source. * * @param source Underlying source for the stream. */ public ReadStream(StreamImpl source) { init(source, null); } /** * Creates a stream and initializes with a specified source. * * @param source Underlying source for the stream. * @param sibling Sibling write stream. */ public ReadStream(StreamImpl source, WriteStream sibling) { init(source, sibling); } /** * Initializes the stream with a given source. * * @param source Underlying source for the stream. * @param sibling Sibling write stream */ public void init(StreamImpl source, WriteStream sibling) { _disableClose = false; _isDisableCloseSource = false; _readTime = 0; if (_source != null && _source != source) { close(); } if (source == null) throw new IllegalArgumentException(); _source = source; _sibling = sibling; if (source.canRead()) { if (_tempRead == null) { _tempRead = TempBuffer.allocate(); _readBuffer = _tempRead._buf; } } _readOffset = 0; _readLength = 0; _readEncoding = null; _readEncodingName = "ISO-8859-1"; } public void setSibling(WriteStream sibling) { _sibling = sibling; } public WriteStream getSibling() { return _sibling; } /** * Returns the underlying source for the stream. * * @return the source */ public StreamImpl getSource() { return _source; } public void setSource(StreamImpl source) { _source = source; } public void setReuseBuffer(boolean reuse) { _reuseBuffer = reuse; } /** * Pushes a filter on the top of the stream stack. * * @param filter the filter to be added. */ public void pushFilter(StreamFilter filter) { filter.init(_source); _source = filter; } public byte []getBuffer() { return _readBuffer; } public int getOffset() { return _readOffset; } public int getLength() { return _readLength; } public void setOffset(int offset) { if (offset < 0) throw new IllegalStateException("illegal offset=" + offset); _readOffset = offset; } /** * Returns the read position. */ public long getPosition() { return _position - (_readLength - _readOffset); } /** * Returns the last read-time. */ public long getReadTime() { if (! _isEnableReadTime) throw new UnsupportedOperationException("last read-time is disabled"); return _readTime; } /** * Returns the last read-time. */ public void clearReadTime() { _readTime = 0; } /** * Enables setting the read time on every reads. */ public void setEnableReadTime(boolean isEnable) { _isEnableReadTime = isEnable; } /** * Sets the current read position. */ public boolean setPosition(long pos) throws IOException { // Allow seeks to be reflected in the char Reader. if (_readEncoding != null) _readEncoding = Encoding.getReadEncoding(this, _readEncodingName); if (pos < 0) { // Return error on seek to negative stream position return false; } else if (pos < getPosition()) { // Seek backwards in the stream _position = pos; _readLength = _readOffset = 0; if (_source != null) { _source.seekStart(pos); return true; } else return false; } else { // Seek forward in the stream, skip any buffered bytes long n = pos - getPosition(); return skip(n) == n; } } /** * Clears the position for statistics cases like a socket stream. */ public void clearPosition() { _position = (_readLength - _readOffset); } /** * Returns true if the stream allows reading. */ public boolean canRead() { return _source.canRead(); } /** * Clears the read buffer. */ public void clearRead() { _readOffset = 0; _readLength = 0; } /** * Returns an estimate of the available bytes. If a read would not block, * it will always return greater than 0. */ public int getAvailable() throws IOException { if (_readOffset < _readLength) { return _readLength - _readOffset; } if (_sibling != null) _sibling.flush(); StreamImpl source = _source; if (source != null) return source.getAvailable(); else return -1; } /** * Returns true if data in the buffer is available. */ public int getBufferAvailable() throws IOException { return _readLength - _readOffset; } /** * Compatibility with InputStream. */ @Override public int available() throws IOException { return getAvailable(); } /** * Returns the next byte or -1 if at the end of file. */ public final int read() throws IOException { if (_readLength <= _readOffset) { if (! readBuffer()) return -1; } return _readBuffer[_readOffset++] & 0xff; } /** * Unreads the last byte. */ public final void unread() { if (_readOffset <= 0) throw new RuntimeException(); _readOffset--; } /** * Waits for data to be available. */ public final boolean waitForRead() throws IOException { if (_readLength <= _readOffset) { if (! readBuffer()) return false; } return true; } /** * Skips the next <code>n</code> bytes. * * @param n bytes to skip. * * @return number of bytes skipped. */ @Override public long skip(long n) throws IOException { if (n <= 0) return n; int skipped = _readLength - _readOffset; if (n < skipped) { _readOffset += n; return n; } _readLength = 0; _readOffset = 0; if (_source.hasSkip()) { if (_sibling != null) _sibling.flush(); long sourceSkipped = _source.skip(n - skipped); if (sourceSkipped < 0) return skipped; else { _position += sourceSkipped; return sourceSkipped + skipped; } } while (_readLength - _readOffset < n - skipped) { skipped += _readLength - _readOffset; _readOffset = 0; _readLength = 0; if (! readBuffer()) { return skipped; } } _readOffset += (int) (n - skipped); return n; } /** * Reads into a byte array. <code>read</code> may return less than * the maximum bytes even if more bytes are available to read. * * @param buf byte array * @param offset offset into the byte array to start reading * @param length maximum byte allowed to read. * * @return number of bytes read or -1 on end of file. */ @Override public final int read(byte []buf, int offset, int length) throws IOException { int readOffset = _readOffset; int readLength = _readLength; if (readLength <= readOffset) { if (ZERO_COPY_SIZE <= length) { if (_sibling != null) _sibling.flush(); int len = _source.read(buf, offset, length); if (len > 0) { _position += len; if (_isEnableReadTime) _readTime = Alarm.getCurrentTime(); } return len; } if (! readBuffer()) return -1; readOffset = _readOffset; readLength = _readLength; } int sublen = readLength - readOffset; if (length < sublen) sublen = length; System.arraycopy(_readBuffer, readOffset, buf, offset, sublen); _readOffset = readOffset + sublen; return sublen; } /** * Reads into a byte array. <code>readAll</code> will always read * <code>length</code> bytes, blocking if necessary, until the end of * file is reached. * * @param buf byte array * @param offset offset into the byte array to start reading * @param length maximum byte allowed to read. * * @return number of bytes read or -1 on end of file. */ public int readAll(byte []buf, int offset, int length) throws IOException { int readLength = 0; while (length > 0) { int sublen = read(buf, offset, length); if (sublen < 0) return readLength == 0 ? -1 : readLength; offset += sublen; readLength += sublen; length -= sublen; } return readLength == 0 ? -1 : readLength; } /* * Reader methods */ /** * Sets the current read encoding. The encoding can either be a * Java encoding name or a mime encoding. * * @param encoding name of the read encoding */ public void setEncoding(String encoding) throws UnsupportedEncodingException { String mimeName = Encoding.getMimeName(encoding); if (mimeName != null && mimeName.equals(_readEncodingName)) return; _readEncoding = Encoding.getReadEncoding(this, encoding); _readEncodingName = mimeName; } /** * Returns the mime-encoding currently read. */ public String getEncoding() { return _readEncodingName; } /** * Reads a character from the stream, returning -1 on end of file. */ public final int readChar() throws IOException { if (_readEncoding != null) { int ch = _readEncoding.read(); return ch; } if (_readLength <= _readOffset) { if (! readBuffer()) return -1; } return _readBuffer[_readOffset++] & 0xff; } /** * Reads into a character buffer from the stream. Like the byte * array version, read may return less characters even though more * characters are available. * * @param buf character buffer to fill * @param offset starting offset into the character buffer * @param length maximum number of characters to read * @return number of characters read or -1 on end of file. */ public final int read(char []buf, int offset, int length) throws IOException { if (_readEncoding != null) return _readEncoding.read(buf, offset, length); byte []readBuffer = _readBuffer; if (readBuffer == null) return -1; int readOffset = _readOffset; int readLength = _readLength; int sublen = readLength - readOffset; if (sublen <= 0) { if (! readBuffer()) { return -1; } readLength = _readLength; readOffset = _readOffset; sublen = readLength - readOffset; } if (length < sublen) sublen = length; for (int i = sublen - 1; i >= 0; i--) buf[offset + i] = (char) (readBuffer[readOffset + i] & 0xff); _readOffset = readOffset + sublen; return sublen; } /** * Reads into a character buffer from the stream. <code>length</code> * characters will always be read until the end of file is reached. * * @param buf character buffer to fill * @param offset starting offset into the character buffer * @param length maximum number of characters to read * @return number of characters read or -1 on end of file. */ public int readAll(char []buf, int offset, int length) throws IOException { int readLength = 0; while (length > 0) { int sublen = read(buf, offset, length); if (sublen <= 0) return readLength > 0 ? readLength : -1; offset += sublen; readLength += sublen; length -= sublen; } return readLength; } /** * Reads characters from the stream, appending to the character buffer. * * @param buf character buffer to fill * @param length maximum number of characters to read * @return number of characters read or -1 on end of file. */ public int read(CharBuffer buf, int length) throws IOException { int len = buf.getLength(); buf.setLength(len + length); int readLength = read(buf.getBuffer(), len, length); if (readLength < 0) buf.setLength(len); else if (readLength < length) buf.setLength(len + readLength); return length; } /** * Reads characters from the stream, appending to the character buffer. * <code>length</code> characters will always be read until the end of * file. * * @param buf character buffer to fill * @param length maximum number of characters to read * @return number of characters read or -1 on end of file. */ public int readAll(CharBuffer buf, int length) throws IOException { int len = buf.getLength(); buf.setLength(len + length); int readLength = readAll(buf.getBuffer(), len, length); if (readLength < 0) buf.setLength(len); else if (readLength < length) buf.setLength(len + readLength); return length; } /** * Reads a line from the stream, returning a string. */ public final String readln() throws IOException { return readLine(); } /** * Reads a line, returning a string. */ public String readLine() throws IOException { CharBuffer cb = _cb; if (cb == null) { cb = _cb = new CharBuffer(); } String result; if (readLine(cb, true)) { result = cb.toString(); cb.clear(); } else if (cb.length() == 0) result = null; else { result = cb.toString(); cb.clear(); } return result; } /** * Reads a line, returning a string. */ public String readLineNoChop() throws IOException { CharBuffer cb = new CharBuffer(); if (readLine(cb, false)) return cb.toString(); else if (cb.length() == 0) return null; else return cb.toString(); } /** * Fills the buffer with the next line from the input stream. * * @return true on success, false on end of file. */ public final boolean readln(CharBuffer cb) throws IOException { return readLine(cb, true); } /** * Reads a line into the character buffer. \r\n is converted to \n. * * @param buf character buffer to fill * @return false on end of file */ public final boolean readLine(CharBuffer cb) throws IOException { return readLine(cb, true); } /** * Reads a line into the character buffer. \r\n is converted to \n. * * @param buf character buffer to fill * @return false on end of file */ public final boolean readLine(CharBuffer cb, boolean isChop) throws IOException { if (_readEncoding != null) return readlnEncoded(cb, isChop); int capacity = cb.getCapacity(); int offset = cb.getLength(); char []buf = cb.getBuffer(); byte []readBuffer = _readBuffer; while (true) { int readOffset = _readOffset; int sublen = _readLength - readOffset; if (capacity - offset < sublen) sublen = capacity - offset; for (; sublen > 0; sublen--) { int ch = readBuffer[readOffset++] & 0xff; if (ch != '\n') { buf[offset++] = (char) ch; } else if (isChop) { if (offset > 0 && buf[offset - 1] == '\r') cb.setLength(offset - 1); else cb.setLength(offset); _readOffset = readOffset; return true; } else { buf[offset++] = (char) '\n'; cb.setLength(offset); _readOffset = readOffset; return true; } } _readOffset = readOffset; if (_readLength <= readOffset) { if (! readBuffer()) { cb.setLength(offset); return offset > 0; } } if (capacity <= offset) { cb.setLength(offset + 1); capacity = cb.getCapacity(); buf = cb.getBuffer(); } } } /** * Reads a line into the character buffer. \r\n is converted to \n. * * @param buf character buffer to fill. * @param length number of characters to fill. * * @return -1 on end of file or the number of characters read. */ public final int readLine(char []buf, int length) throws IOException { return readLine(buf, length, true); } /** * Reads a line into the character buffer. \r\n is converted to \n. * * @param buf character buffer to fill. * @param length number of characters to fill. * * @return -1 on end of file or the number of characters read. */ public final int readLine(char []buf, int length, boolean isChop) throws IOException { byte []readBuffer = _readBuffer; int offset = 0; while (true) { int readOffset = _readOffset; int sublen = _readLength - readOffset; if (sublen < length) sublen = length; for (; sublen > 0; sublen--) { int ch = readBuffer[readOffset++] & 0xff; if (ch != '\n') { } else if (isChop) { _readOffset = readOffset; if (offset > 0 && buf[offset - 1] == '\r') return offset - 1; else return offset; } else { buf[offset++] = (char) ch; _readOffset = readOffset; return offset + 1; } buf[offset++] = (char) ch; } _readOffset = readOffset; if (readOffset <= _readLength) { if (! readBuffer()) { return offset; } } if (length <= offset) return length + 1; } } private boolean readlnEncoded(CharBuffer cb, boolean isChop) throws IOException { while (true) { int ch = readChar(); if (ch < 0) return cb.length() > 0; if (ch != '\n') { } else if (isChop) { if (cb.length() > 0 && cb.getLastChar() == '\r') cb.setLength(cb.getLength() - 1); return true; } else { cb.append('\n'); return true; } cb.append((char) ch); } } // // data api // /** * Reads a 4-byte network encoded integer */ public int readInt() throws IOException { if (_readOffset + 4 < _readLength) { return (((_readBuffer[_readOffset++] & 0xff) << 24) + ((_readBuffer[_readOffset++] & 0xff) << 16) + ((_readBuffer[_readOffset++] & 0xff) << 8) + ((_readBuffer[_readOffset++] & 0xff))); } else { return ((read() << 24) + (read() << 16) + (read() << 8) + (read())); } } /** * Reads an 8-byte network encoded long */ public long readLong() throws IOException { return (((long) read() << 56) + ((long) read() << 48) + ((long) read() << 40) + ((long) read() << 32) + ((long) read() << 24) + ((long) read() << 16) + ((long) read() << 8) + ((long) read())); } /** * Reads a utf-8 string */ public int readUTF8ByByteLength(char []buffer, int offset, int byteLength) throws IOException { int k = 0; for (int i = 0; i < byteLength; i++) { if (_readLength <= _readOffset) { readBuffer(); } int ch = _readBuffer[_readOffset++]; if (ch < 0x80) buffer[k++] = (char) ch; else if ((ch & 0xe0) == 0xc0) { int c2 = read(); i += 1; buffer[k++] = (char) (((ch & 0x1f) << 6) + (c2 & 0x3f)); } else { int c2 = read(); int c3 = read(); i += 2; buffer[k++] = (char) (((ch & 0x1f) << 12) + ((c2 & 0x3f) << 6) + ((c3 & 0x3f))); } } return k; } /** * Copies this stream to the output stream. * * @param os destination stream. */ public void writeToStream(OutputStream os) throws IOException { if (_readLength <= _readOffset) { readBuffer(); } while (_readOffset < _readLength) { os.write(_readBuffer, _readOffset, _readLength - _readOffset); readBuffer(); } } /** * Writes <code>len<code> bytes to the output stream from this stream. * * @param os destination stream. * @param len bytes to write. */ public void writeToStream(OutputStream os, int len) throws IOException { while (len > 0) { if (_readLength <= _readOffset) { if (! readBuffer()) return; } int sublen = _readLength - _readOffset; if (len < sublen) sublen = len; os.write(_readBuffer, _readOffset, sublen); _readOffset += sublen; len -= sublen; } } /** * Copies this stream to the output stream. * * @param out destination writer */ public void writeToWriter(Writer out) throws IOException { int ch; while ((ch = readChar()) >= 0) out.write((char) ch); } /** * Fills the buffer from the underlying stream. */ public int fillBuffer() throws IOException { if (! readBuffer()) return -1; else return _readLength; } /** * Fills the buffer with a non-blocking read. */ public boolean readNonBlock() throws IOException { if (_readOffset < _readLength) return true; if (_readBuffer == null) { _readOffset = 0; _readLength = 0; return false; } if (_sibling != null) _sibling.flush(); _readOffset = 0; int readLength = _source.readNonBlock(_readBuffer, 0, _readBuffer.length); // Setting to 0 is needed to avoid int to long conversion errors with AIX if (readLength > 0) { _readLength = readLength; _position += readLength; if (_isEnableReadTime) _readTime = Alarm.getCurrentTime(); return true; } else { _readLength = 0; return false; } } /** * Fills the buffer with a non-blocking read. * * @param timeout the timeout in milliseconds for the next data * @return true on data or end of file, false on timeout */ public int fillWithTimeout(long timeout) throws IOException { if (_readOffset < _readLength) return _readLength - _readOffset; if (_readBuffer == null) { _readOffset = 0; _readLength = 0; return -1; } if (_sibling != null) _sibling.flush(); _readOffset = 0; StreamImpl source = _source; if (source == null) { // return true on end of file return -1; } int readLength = source.readTimeout(_readBuffer, 0, _readBuffer.length, timeout); if (readLength > 0) { _readLength = readLength; _position += readLength; if (_isEnableReadTime) _readTime = Alarm.getCurrentTime(); return readLength; } else if (readLength == READ_TIMEOUT) { // timeout _readLength = 0; return 0; } else { // return false on end of file _readLength = 0; return -1; } } /** * Fills the buffer with a timed read, testing for the end of file. * Used for cases like comet to test if the read stream has closed. * * @param timeout the timeout in milliseconds for the next data * @return true on data or timeout, false on end of file */ public boolean fillIfLive(long timeout) throws IOException { StreamImpl source = _source; byte []readBuffer = _readBuffer; if (readBuffer == null || source == null) { _readOffset = 0; _readLength = 0; return false; } if (_readOffset > 0) { System.arraycopy(readBuffer, _readOffset, readBuffer, 0, _readLength - _readOffset); _readLength -= _readOffset; _readOffset = 0; } if (_readLength == readBuffer.length) return true; if (_sibling != null) _sibling.flush(); int readLength = source.readTimeout(_readBuffer, _readLength, _readBuffer.length - _readLength, timeout); if (readLength >= 0) { _readLength += readLength; _position += readLength; if (_isEnableReadTime) _readTime = Alarm.getCurrentTime(); return true; } else if (readLength == READ_TIMEOUT) { // timeout return true; } else { // return false on end of file return false; } } /** * Fills the read buffer, flushing the write buffer. * * @return false on end of file and true if there's more data. */ private boolean readBuffer() throws IOException { if (_readBuffer == null || _source == null) { _readOffset = 0; _readLength = 0; return false; } if (_sibling != null) _sibling.flush(); _readOffset = 0; _readLength = 0; int readLength = _source.read(_readBuffer, 0, _readBuffer.length); // Setting to 0 is needed to avoid int to long conversion errors with AIX if (readLength > 0) { _readLength = readLength; _position += readLength; if (_isEnableReadTime) _readTime = Alarm.getCurrentTime(); return true; } else { _readLength = 0; return false; } } /** * Disables close. Sometimes an application will pass a stream * to a client that may close the stream at an inappropriate time. * Setting disable close gives the calling routine control over closing * the stream. */ public void setDisableClose(boolean disableClose) { _disableClose = disableClose; } /** * Disables closing of the underlying source. */ public void setDisableCloseSource(boolean disableClose) { _isDisableCloseSource = disableClose; } /** * Close the stream. */ @Override public final void close() { try { if (_disableClose) return; if (! _reuseBuffer) { if (_tempRead != null) { TempBuffer.free(_tempRead); _tempRead = null; } _readBuffer = null; } if (_readEncoding != null) { Reader reader = _readEncoding; _readEncoding = null; reader.close(); } if (_source != null && ! _isDisableCloseSource) { StreamImpl s = _source; _source = null; s.close(); } } catch (IOException e) { log().log(Level.FINE, e.toString(), e); } } /** * Returns a named attribute. For example, an HTTP stream * may use this to return header values. */ public Object getAttribute(String name) throws IOException { if (_sibling != null) _sibling.flush(); return _source.getAttribute(name); } /** * Lists all named attributes. */ public Iterator<String> getAttributeNames() throws IOException { if (_sibling != null) _sibling.flush(); return _source.getAttributeNames(); } /** * Sets a named attribute. For example, an HTTP stream * may use this to set header values. */ public void setAttribute(String name, Object value) throws IOException { _source.setAttribute(name, value); } /** * Removes a named attribute. */ public void removeAttribute(String name) throws IOException { _source.removeAttribute(name); } /** * Returns the Path which opened this stream. */ public Path getPath() { return _source == null ? null : _source.getPath(); } /** * Returns the user path which opened this stream. * * <p>Parsing routines typically use this for error reporting. */ public String getUserPath() { if (_source == null || _source.getPath() == null) return "stream"; else return _source.getPath().getUserPath(); } /** * Returns the user path which opened this stream. * * <p>Parsing routines typically use this for error reporting. */ public String getURL() { if (_source == null || _source.getPath() == null) return "stream:"; else return _source.getPath().getURL(); } /** * Sets a path name associated with the stream. */ public void setPath(Path path) { _source.setPath(path); } /** * Returns a Reader reading to this stream. */ public Reader getReader() { if (_reader == null) _reader = new StreamReader(); return _reader; } private static Logger log() { return Logger.getLogger(ReadStream.class.getName()); } /** * Returns a printable representation of the read stream. */ public String toString() { return "ReadStream[" + _source + "]"; } public boolean lock(boolean shared, boolean block) { if (! (_source instanceof LockableStream)) return true; LockableStream ls = (LockableStream) _source; return ls.lock(shared, block); } public boolean unlock() { if (! (_source instanceof LockableStream)) return true; LockableStream ls = (LockableStream) _source; return ls.unlock(); } public class StreamReader extends Reader { public final int read() throws IOException { return ReadStream.this.readChar(); } public final int read(char []cbuf, int off, int len) throws IOException { return ReadStream.this.read(cbuf, off, len); } public boolean ready() throws IOException { return ReadStream.this.available() > 0; } public final void close() throws IOException { } public ReadStream getStream() { return ReadStream.this; } } }