com.caucho.vfs.ReadStream Maven / Gradle / Ivy
Show all versions of resin-kernel Show documentation
/*
* Copyright (c) 1998-2012 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.CharBuffer;
import com.caucho.util.CurrentTime;
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.
*
* 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.
*
*
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 init
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 void setOffset(int offset)
{
if (offset < 0)
throw new IllegalStateException("illegal offset=" + offset);
_readOffset = offset;
}
public int getLength()
{
return _readLength;
}
public void setLength(int length)
{
if (length< 0 || _readBuffer.length < length)
throw new IllegalStateException("illegal length=" + length);
_readLength = length;
}
/**
* 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.
*/
@Override
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 n
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. read
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();
StreamImpl source = _source;
if (source == null) {
return -1;
}
int len = source.read(buf, offset, length);
if (len > 0) {
_position += len;
if (_isEnableReadTime)
_readTime = CurrentTime.getCurrentTime();
}
return len;
}
if (! readBuffer())
return -1;
readOffset = _readOffset;
readLength = _readLength;
}
int sublen = Math.min(length, readLength - readOffset);
System.arraycopy(_readBuffer, readOffset, buf, offset, sublen);
_readOffset = readOffset + sublen;
return sublen;
}
/**
* Reads into a byte array. readAll
will always read
* length
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 = Math.min(length, readLength - readOffset);
if (readLength <= readOffset) {
if (! readBuffer()) {
return -1;
}
readLength = _readLength;
readOffset = _readOffset;
sublen = Math.min(length, readLength - readOffset);
}
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. length
* 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.
* length
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 = Math.min(capacity - offset, _readLength - readOffset);
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 = Math.min(length, _readLength - readOffset);
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++] & 0xff;
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 len 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 = Math.min(len, _readLength - _readOffset);
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 = CurrentTime.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 = CurrentTime.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 = CurrentTime.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 = CurrentTime.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 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.
*
* 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.
*
*
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;
}
}
}