/* Copyright (c) 2007 Jython Developers */
package org.python.core.io;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import org.python.core.Py;
import org.python.core.PyArray;
import org.python.core.PyObject;
import org.python.core.PyString;
/**
* Base class for text I/O.
*
* This class provides a character and line based interface to stream
* I/O.
*
* @author Philip Jenvey
*/
public abstract class TextIOBase extends IOBase {
/** The size of chunks read for readline */
public static final int CHUNK_SIZE = 300;
/** Byte representation of the Carriage Return character */
protected static final byte CR_BYTE = 13;
/** The underlying buffered i/o stream */
protected BufferedIOBase bufferedIO;
/** The readahead buffer. Though the underlying stream is
* sometimes buffered, readahead is specific to the TextIO layer
* to mostly benefit readline processing */
protected ByteBuffer readahead;
/** Builds the final String returned from readline */
protected StringBuilder builder;
/** An interim buffer for builder; readline loops move bytes into
* this array to avoid StringBuilder.append method calls */
protected char[] interimBuilder;
/**
* Contruct a TextIOBase wrapping the given BufferedIOBase.
*
* @param bufferedIO a BufferedIOBase to wrap
*/
public TextIOBase(BufferedIOBase bufferedIO) {
this.bufferedIO = bufferedIO;
readahead = ByteBuffer.allocate(CHUNK_SIZE);
readahead.flip();
builder = new StringBuilder(CHUNK_SIZE);
interimBuilder = new char[CHUNK_SIZE];
}
/**
* Read and return up to size bytes, contained in a String.
*
* Returns an empty String on EOF
*
* @param size the number of bytes to read
* @return a String containing the bytes read
*/
public String read(int size) {
unsupported("read");
return null;
}
/**
* Read until EOF.
*
* @return a String containing the bytes read
*/
public String readall() {
unsupported("readall");
return null;
}
/**
* Read until size, newline or EOF.
*
* Returns an empty string if EOF is hit immediately.
*
* @param size the number of bytes to read
* @return a String containing the bytes read
*/
public String readline(int size) {
unsupported("read");
return null;
}
/**
* Read into the given PyObject that implements the read-write
* buffer interface (currently just a PyArray).
*
* @param buf a PyObject implementing the read-write buffer interface
* @return the amount of data read as an int
*/
public int readinto(PyObject buf) {
// This is an inefficient version of readinto: but readinto is
// not recommended for use in Python 2.x anyway
if (!(buf instanceof PyArray)) {
// emulate PyArg_ParseTuple
if (buf instanceof PyString) {
throw Py.TypeError("Cannot use string as modifiable buffer");
}
throw Py.TypeError("argument 1 must be read-write buffer, not "
+ buf.getType().fastGetName());
}
PyArray array = (PyArray)buf;
String read = read(array.__len__());
for (int i = 0; i < read.length(); i++) {
array.set(i, new PyString(read.charAt(i)));
}
return read.length();
}
/**
* Write the given String to the IO stream.
*
* Returns the number of characters written.
*
* @param buf a String value
* @return the number of characters written as an int
*/
public int write(String buf) {
unsupported("write");
return -1;
}
@Override
public long truncate(long pos) {
long initialPos = tell();
flush();
pos = bufferedIO.truncate(pos);
// FileChannel resets the position to the truncated size if
// the position was larger, whereas Python expects the
// original position
if (initialPos > pos) {
seek(initialPos);
}
return pos;
}
@Override
public void flush() {
bufferedIO.flush();
}
@Override
public void close() {
bufferedIO.close();
}
@Override
public long seek(long pos, int whence) {
pos = bufferedIO.seek(pos, whence);
clearReadahead();
return pos;
}
@Override
public long tell() {
return bufferedIO.tell() - readahead.remaining();
}
@Override
public RawIOBase fileno() {
return bufferedIO.fileno();
}
@Override
public boolean isatty() {
return bufferedIO.isatty();
}
@Override
public boolean readable() {
return bufferedIO.readable();
}
@Override
public boolean writable() {
return bufferedIO.writable();
}
@Override
public boolean closed() {
return bufferedIO.closed();
}
@Override
public InputStream asInputStream() {
return bufferedIO.asInputStream();
}
@Override
public OutputStream asOutputStream() {
return bufferedIO.asOutputStream();
}
/**
* Return the known Newline types, as a PyObject, encountered
* while reading this file.
*
* Returns None for all modes except universal newline mode.
*
* @return a PyObject containing all encountered Newlines, or None
*/
public PyObject getNewlines() {
return Py.None;
}
/**
* Return true if the file pointer is currently at EOF.
*
* If the file pointer is not at EOF, the readahead will contain
* at least one byte after this method is called.
*
* @return true if the file pointer is currently at EOF
*/
protected boolean atEOF() {
return readahead.hasRemaining() ? false : readChunk() == 0;
}
/**
* Read a chunk of data of size CHUNK_SIZE into the readahead
* buffer. Returns the amount of data read.
*
* @return the amount of data read
*/
protected int readChunk() {
// Prepare the readahead for reading
readahead.clear();
if (readahead.remaining() > CHUNK_SIZE) {
// Limit potential full reads on a resized readahead to CHUNK_SIZE
readahead.limit(readahead.position() + CHUNK_SIZE);
}
bufferedIO.read1(readahead);
readahead.flip();
return readahead.remaining();
}
/**
* Read a chunk of data of the given size into the readahead
* buffer. Returns the amount of data read.
*
* Enforces a minimum size of CHUNK_SIZE.
*
* @param size the desired size of the chunk
* @return the amount of data read
*/
protected int readChunk(int size) {
// Prepare the readahead for reading
if (size > CHUNK_SIZE) {
// More than we can hold; reallocate a larger readahead
readahead = ByteBuffer.allocate(size);
} else {
size = CHUNK_SIZE;
readahead.clear().limit(size);
}
bufferedIO.readinto(readahead);
readahead.flip();
return readahead.remaining();
}
/**
* Restore the readahead to its original size (CHUNK_SIZE) if it
* was previously resized.
*
* The readahead contents are preserved. This method assumes it
* contains a number of remaining elements less than or equal to
* CHUNK_SIZE.
*
*/
protected void packReadahead() {
if (readahead.capacity() > CHUNK_SIZE) {
ByteBuffer old = readahead;
readahead = ByteBuffer.allocate(CHUNK_SIZE);
readahead.put(old);
readahead.flip();
}
}
/**
* Clear and reset the readahead buffer.
*
*/
protected void clearReadahead() {
readahead.clear().flip();
}
/**
* Return the String result of the builder, and reset it/perform
* cleanup on it.
*
* @return the result of builder.toString()
*/
protected String drainBuilder() {
String result = builder.toString();
if (builder.capacity() > CHUNK_SIZE) {
// The builder was resized; potentially to a large
// value. Create a smaller one so the old one can be
// garbage collected
builder = new StringBuilder(CHUNK_SIZE);
} else {
builder.setLength(0);
}
return result;
}
}