/* Copyright (c) 2007 Jython Developers */
package org.python.core.io;
import java.nio.ByteBuffer;
import java.util.regex.Pattern;
/**
* A Buffered text stream.
*
* This differs from py3k TextIOWrapper, which currently handles both
* text mode (py3k text mode is incompatible with Python 2.x's text
* mode) as well as universal mode.
*
* @see UniversalIOWrapper
*
* @author Philip Jenvey
*/
public class TextIOWrapper extends BinaryIOWrapper {
/** LF RE pattern, used for writes */
private static final Pattern LF_PATTERN = Pattern.compile("\n");
/** The platform's Newline character */
private String newline;
/** Whether or not newline is \n */
private boolean newlineIsLF;
/**
* Contruct a TextIOWrapper wrapping the given BufferedIOBase.
*
* @param bufferedIO {@inheritDoc}
*/
public TextIOWrapper(BufferedIOBase bufferedIO) {
super(bufferedIO);
newline = System.getProperty("line.separator");
newlineIsLF = newline.equals("\n");
}
@Override
public String read(int size) {
if (newlineIsLF) {
return super.read(size);
}
if (size < 0) {
return readall();
}
// Avoid ByteBuffer (this.readahead) method calls in the inner
// loop by reading directly from the readahead's backing array
byte[] readaheadArray;
int readaheadPos;
char[] builderArray = new char[size];
int builderPos = 0;
do {
readaheadArray = readahead.array();
readaheadPos = readahead.position();
while (readaheadPos < readahead.limit() && builderPos < size) {
char next = (char)(readaheadArray[readaheadPos++] & 0xff);
if (next == '\r') {
// Don't translate CR at EOF
if (readaheadPos == readahead.limit()) {
if (readChunk() == 0) {
// EOF
builderArray[builderPos++] = next;
return new String(builderArray, 0, builderPos);
}
// Not EOF and readChunk replaced the
// readahead; reset the readahead info
readaheadArray = readahead.array();
readaheadPos = readahead.position();
}
if (readaheadArray[readaheadPos] == LF_BYTE) {
next = '\n';
readaheadPos++;
}
}
builderArray[builderPos++] = next;
}
} while (builderPos < size && readChunk(size - builderPos) > 0);
// Finally reposition the readahead to where we ended. The
// position is invalid if the readahead is empty (at EOF;
// readChunk() returned 0)
if (readahead.hasRemaining()) {
readahead.position(readaheadPos);
}
// Shrink the readahead if it grew
packReadahead();
return new String(builderArray, 0, builderPos);
}
@Override
public String readall() {
if (newlineIsLF) {
return super.readall();
}
// Read the remainder of file
ByteBuffer remaining = bufferedIO.readall();
// Detect CRLF spanning the readahead and remaining
int length = 0;
if (readahead.hasRemaining() && readahead.get(readahead.limit() - 1) == CR_BYTE &&
remaining.hasRemaining() && remaining.get(remaining.position()) == LF_BYTE) {
// The trailing value of readahead is CR, the first value
// of remaining is LF. Overwrite CR with that LF (CRLF ->
// LF)
length--;
}
// Create an array that accommodates the readahead and the
// remainder
char[] all = new char[readahead.remaining() + remaining.remaining()];
// Consume the readahead
length += readLoop(readahead.array(), readahead.position(), all, 0, readahead.remaining());
readahead.position(readahead.limit());
// Consume the remainder of the file
length += readLoop(remaining.array(), remaining.position(), all, length,
remaining.remaining());
return new String(all, 0, length);
}
/**
* Read and convert the src byte array into the dest char array.
*
* Converts CRLF to LF. src is assumed to be the end of the file;
* that is, when src ends with CR, no attempt is made to peek into
* the underlying file to detect a succeeding LF.
*
* @param src the source byte array
* @param srcPos starting position in the source array
* @param dest the destination char array
* @param destPos starting position in the destination array
* @param length the number of array elements to be copied
* @return the number of chars written to the destination array
*/
private int readLoop(byte[] src, int srcPos, char[] dest, int destPos, int length) {
int destStartPos = destPos;
int srcEndPos = srcPos + length;
while (srcPos < srcEndPos) {
char next = (char)(src[srcPos++] & 0xff);
if (next == '\r') {
// Don't translate CR at EOF
if (srcPos == srcEndPos) {
dest[destPos++] = next;
continue;
}
if (src[srcPos] == LF_BYTE) {
next = '\n';
srcPos++;
}
}
dest[destPos++] = next;
}
return destPos - destStartPos;
}
@Override
public String readline(int size) {
if (newlineIsLF) {
return super.readline(size);
}
// Avoid ByteBuffer (this.readahead) and StringBuilder
// (this.builder) method calls in the inner loop by reading
// directly from the readahead's backing array and writing to
// an interim char array (this.interimBuilder)
byte[] readaheadArray;
int readaheadPos;
int interimBuilderPos;
do {
readaheadArray = readahead.array();
readaheadPos = readahead.position();
interimBuilderPos = 0;
while (readaheadPos < readahead.limit() &&
(size < 0 || builder.length() + interimBuilderPos < size)) {
char next = (char)(readaheadArray[readaheadPos++] & 0xff);
interimBuilder[interimBuilderPos++] = next;
if (next == '\r') {
boolean flushInterimBuilder = false;
// Don't translate CR at EOF
if (readaheadPos == readahead.limit()) {
if (readChunk() == 0) {
// EOF
builder.append(interimBuilder, 0, interimBuilderPos);
return drainBuilder();
}
// Not EOF and readChunk replaced the
// readahead; reset the readahead info and
// flush the interimBuilder (it's full)
readaheadArray = readahead.array();
readaheadPos = readahead.position();
flushInterimBuilder = true;
}
if (readaheadArray[readaheadPos] == LF_BYTE) {
// A CRLF: overwrite CR with the LF
readaheadPos++;
interimBuilder[interimBuilderPos - 1] = next = '\n';
}
if (flushInterimBuilder) {
// Safe to flush now, incase a CRLF was
// changed
builder.append(interimBuilder, 0, interimBuilderPos);
interimBuilderPos = 0;
}
}
if (next == '\n') {
builder.append(interimBuilder, 0, interimBuilderPos);
// Reposition the readahead to where we ended
readahead.position(readaheadPos);
return drainBuilder();
}
}
builder.append(interimBuilder, 0, interimBuilderPos);
} while ((size < 0 || builder.length() < size) && readChunk() > 0);
// Finally reposition the readahead to where we ended. The
// position is invalid if the readahead is empty (at EOF;
// readChunk() returned 0)
if (readahead.hasRemaining()) {
readahead.position(readaheadPos);
}
return drainBuilder();
}
@Override
public int write(String buf) {
if (!newlineIsLF) {
buf = LF_PATTERN.matcher(buf).replaceAll(newline);
}
return super.write(buf);
}
}