/* 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; } }