blob: 354cac9f1839bc8aa95f656b37d82ed44b812684 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.io.input;
import static org.apache.commons.io.IOUtils.EOF;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.CharBuffer;
/**
* Reader proxy that transparently writes a copy of all characters read from the proxied reader to a given Reader. Using
* {@link #skip(long)} or {@link #mark(int)}/{@link #reset()} on the reader will result on some characters from the
* reader being skipped or duplicated in the writer.
* <p>
* The proxied reader is closed when the {@link #close()} method is called on this proxy. You may configure whether the
* reader closes the writer.
* </p>
*
* @since 2.7
*/
public class TeeReader extends ProxyReader {
/**
* The writer that will receive a copy of all characters read from the proxied reader.
*/
private final Writer branch;
/**
* Flag for closing the associated writer when this reader is closed.
*/
private final boolean closeBranch;
/**
* Creates a TeeReader that proxies the given {@link Reader} and copies all read characters to the given
* {@link Writer}. The given writer will not be closed when this reader gets closed.
*
* @param input reader to be proxied
* @param branch writer that will receive a copy of all characters read
*/
public TeeReader(final Reader input, final Writer branch) {
this(input, branch, false);
}
/**
* Creates a TeeReader that proxies the given {@link Reader} and copies all read characters to the given
* {@link Writer}. The given writer will be closed when this reader gets closed if the closeBranch parameter is
* {@code true}.
*
* @param input reader to be proxied
* @param branch writer that will receive a copy of all characters read
* @param closeBranch flag for closing also the writer when this reader is closed
*/
public TeeReader(final Reader input, final Writer branch, final boolean closeBranch) {
super(input);
this.branch = branch;
this.closeBranch = closeBranch;
}
/**
* Closes the proxied reader and, if so configured, the associated writer. An exception thrown from the reader will
* not prevent closing of the writer.
*
* @throws IOException if either the reader or writer could not be closed
*/
@Override
public void close() throws IOException {
try {
super.close();
} finally {
if (closeBranch) {
branch.close();
}
}
}
/**
* Reads a single character from the proxied reader and writes it to the associated writer.
*
* @return next character from the reader, or -1 if the reader has ended
* @throws IOException if the reader could not be read (or written)
*/
@Override
public int read() throws IOException {
final int ch = super.read();
if (ch != EOF) {
branch.write(ch);
}
return ch;
}
/**
* Reads characters from the proxied reader and writes the read characters to the associated writer.
*
* @param chr character buffer
* @return number of characters read, or -1 if the reader has ended
* @throws IOException if the reader could not be read (or written)
*/
@Override
public int read(final char[] chr) throws IOException {
final int n = super.read(chr);
if (n != EOF) {
branch.write(chr, 0, n);
}
return n;
}
/**
* Reads characters from the proxied reader and writes the read characters to the associated writer.
*
* @param chr character buffer
* @param st start offset within the buffer
* @param end maximum number of characters to read
* @return number of characters read, or -1 if the reader has ended
* @throws IOException if the reader could not be read (or written)
*/
@Override
public int read(final char[] chr, final int st, final int end) throws IOException {
final int n = super.read(chr, st, end);
if (n != EOF) {
branch.write(chr, st, n);
}
return n;
}
/**
* Reads characters from the proxied reader and writes the read characters to the associated writer.
*
* @param target character buffer
* @return number of characters read, or -1 if the reader has ended
* @throws IOException if the reader could not be read (or written)
*/
@Override
public int read(final CharBuffer target) throws IOException {
final int originalPosition = target.position();
final int n = super.read(target);
if (n != EOF) {
// Appending can only be done after resetting the CharBuffer to the
// right position and limit.
final int newPosition = target.position();
final int newLimit = target.limit();
try {
target.position(originalPosition).limit(newPosition);
branch.append(target);
} finally {
// Reset the CharBuffer as if the appending never happened.
target.position(newPosition).limit(newLimit);
}
}
return n;
}
}