blob: 92f4ab3b68c6ff1e0a9536eeaa67f536138f201e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2017 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.team.internal.core.streams;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
/**
* Converts LFs in the underlying input stream to CR/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 LFtoCRLFInputStream extends FilterInputStream {
private boolean mustReturnLF = false;
/**
* Creates a new filtered input stream.
* @param in the underlying input stream
*/
public LFtoCRLFInputStream(InputStream in) {
super(in);
}
/**
* Wraps the underlying stream's method.
* Translates LFs to CR/LF sequences 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
*/
@Override
public int read() throws IOException {
if (mustReturnLF) {
mustReturnLF = false;
return '\n';
}
int b = in.read(); // ok if this throws
if (b == '\n') {
mustReturnLF = true;
b = '\r';
}
return b;
}
/**
* Wraps the underlying stream's method.
* Translates LFs to CR/LF sequences 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
*/
@Override
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;
}
// prefix with remembered \n from last read, but don't expand it a second time
int count = 0;
if (mustReturnLF) {
mustReturnLF = false;
buffer[off++] = '\n';
--len;
count = 1;
if (len < 2) return count; // is there still enough room to expand more?
}
// read some bytes from the stream into the back half of the buffer
// this guarantees that there is always room to expand
len /= 2;
int j = off + len;
InterruptedIOException iioe = null;
try {
len = in.read(buffer, j, len);
if (len == -1) {
return (count == 0) ? -1 : count;
}
} catch (InterruptedIOException e) {
len = e.bytesTransferred;
iioe = e;
}
count += len;
// copy bytes from the middle to the front of the array, expanding LF->CR/LF
while (len-- > 0) {
byte b = buffer[j++];
if (b == '\n') {
buffer[off++] = '\r';
count++;
}
buffer[off++] = b;
}
if (iioe != null) {
iioe.bytesTransferred = count;
throw iioe;
}
return count;
}
/**
* 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
*/
@Override
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 LFs to CR/LF sequences in these bytes.
* @throws IOException if an i/o error occurs
*/
@Override
public int available() throws IOException {
return in.available(); // we can guarantee at least this amount after expansion
}
/**
* Mark is not supported by the wrapper even if the underlying stream does, returns false.
*/
@Override
public boolean markSupported() {
return false;
}
}