blob: a5db44884fd7559acb2e80e3c75bd425e8ae3c1f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2002 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v0.5
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v05.html
*
* Contributors:
* IBM - Initial API and implementation
******************************************************************************/
package org.eclipse.team.internal.ccvs.core.streams;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
/**
* Converts CR/LFs in the underlying input stream to LF.
*
* Supports resuming partially completed operations after an InterruptedIOException
* if the underlying stream does. Check the bytesTransferred field to determine how
* much of the operation completed; conversely, at what point to resume.
*/
public class CRLFtoLFInputStream extends FilterInputStream {
private boolean pendingByte = false;
private int lastByte = -1;
/**
* Creates a new filtered input stream.
* @param in the underlying input stream
*/
public CRLFtoLFInputStream(InputStream in) {
super(in);
}
/**
* Wraps the underlying stream's method.
* Translates CR/LF sequences to LFs transparently.
* @throws InterruptedIOException if the operation was interrupted before all of the
* bytes specified have been skipped, bytesTransferred will be zero
* @throws IOException if an i/o error occurs
*/
public int read() throws IOException {
if (! pendingByte) {
lastByte = in.read(); // ok if this throws
pendingByte = true; // remember the byte in case we throw an exception later on
}
if (lastByte == '\r') {
lastByte = in.read(); // ok if this throws
if (lastByte != '\n') {
if (lastByte == -1) pendingByte = false;
return '\r'; // leaves the byte pending for later
}
}
pendingByte = false;
return lastByte;
}
/**
* Wraps the underlying stream's method.
* Translates CR/LF sequences to LFs transparently.
* @throws InterruptedIOException if the operation was interrupted before all of the
* bytes specified have been skipped, bytesTransferred may be non-zero
* @throws IOException if an i/o error occurs
*/
public int read(byte[] buffer, int off, int len) throws IOException {
// handle boundary cases cleanly
if (len == 0) {
return 0;
} else if (len == 1) {
int b = read();
if (b == -1) return -1;
buffer[off] = (byte) b;
return 1;
}
// read some bytes from the stream
// prefix with pending byte from last read if any
int count = 0;
if (pendingByte) {
buffer[off] = (byte) lastByte;
pendingByte = false;
count = 1;
}
InterruptedIOException iioe = null;
try {
len = in.read(buffer, off + count, len - count);
if (len == -1) {
return (count == 0) ? -1 : count;
}
} catch (InterruptedIOException e) {
len = e.bytesTransferred;
iioe = e;
}
count += len;
// strip out CR's in CR/LF pairs
// pendingByte will be true iff previous byte was a CR
int j = off;
for (int i = off; i < off + count; ++i) { // invariant: j <= i
lastByte = buffer[i];
if (lastByte == '\r') {
if (pendingByte) {
buffer[j++] = '\r'; // write out orphan CR
} else {
pendingByte = true;
}
} else {
if (pendingByte) {
if (lastByte != '\n') buffer[j++] = '\r'; // if LF, don't write the CR
pendingByte = false;
}
buffer[j++] = (byte) lastByte;
}
}
if (iioe != null) {
iioe.bytesTransferred = j - off;
throw iioe;
}
return j - off;
}
/**
* Calls read() to skip the specified number of bytes
* @throws InterruptedIOException if the operation was interrupted before all of the
* bytes specified have been skipped, bytesTransferred may be non-zero
* @throws IOException if an i/o error occurs
*/
public long skip(long count) throws IOException {
int actualCount = 0; // assumes count < Integer.MAX_INT
try {
while (count-- > 0 && read() != -1) actualCount++; // skip the specified number of bytes
return actualCount;
} catch (InterruptedIOException e) {
e.bytesTransferred = actualCount;
throw e;
}
}
/**
* Wraps the underlying stream's method.
* Returns the number of bytes that can be read without blocking; accounts for
* possible translation of CR/LF sequences to LFs in these bytes.
* @throws IOException if an i/o error occurs
*/
public int available() throws IOException {
return in.available() / 2; // we can guarantee at least this amount after contraction
}
/**
* Mark is not supported by the wrapper even if the underlying stream does, returns false.
*/
public boolean markSupported() {
return false;
}
}