001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.io.input;
018
019import static org.apache.commons.io.IOUtils.EOF;
020
021import java.io.IOException;
022import java.io.Reader;
023import java.io.Writer;
024import java.nio.CharBuffer;
025
026/**
027 * Reader proxy that transparently writes a copy of all characters read from the proxied reader to a given Reader. Using
028 * {@link #skip(long)} or {@link #mark(int)}/{@link #reset()} on the reader will result on some characters from the
029 * reader being skipped or duplicated in the writer.
030 * <p>
031 * The proxied reader is closed when the {@link #close()} method is called on this proxy. You may configure whether the
032 * reader closes the writer.
033 * </p>
034 *
035 * @since 2.7
036 */
037public class TeeReader extends ProxyReader {
038
039    /**
040     * The writer that will receive a copy of all characters read from the proxied reader.
041     */
042    private final Writer branch;
043
044    /**
045     * Flag for closing the associated writer when this reader is closed.
046     */
047    private final boolean closeBranch;
048
049    /**
050     * Constructs a TeeReader that proxies the given {@link Reader} and copies all read characters to the given
051     * {@link Writer}. The given writer will not be closed when this reader gets closed.
052     *
053     * @param input  reader to be proxied
054     * @param branch writer that will receive a copy of all characters read
055     */
056    public TeeReader(final Reader input, final Writer branch) {
057        this(input, branch, false);
058    }
059
060    /**
061     * Constructs a TeeReader that proxies the given {@link Reader} and copies all read characters to the given
062     * {@link Writer}. The given writer will be closed when this reader gets closed if the closeBranch parameter is
063     * {@code true}.
064     *
065     * @param input       reader to be proxied
066     * @param branch      writer that will receive a copy of all characters read
067     * @param closeBranch flag for closing also the writer when this reader is closed
068     */
069    public TeeReader(final Reader input, final Writer branch, final boolean closeBranch) {
070        super(input);
071        this.branch = branch;
072        this.closeBranch = closeBranch;
073    }
074
075    /**
076     * Closes the proxied reader and, if so configured, the associated writer. An exception thrown from the reader will
077     * not prevent closing of the writer.
078     *
079     * @throws IOException if either the reader or writer could not be closed
080     */
081    @Override
082    public void close() throws IOException {
083        try {
084            super.close();
085        } finally {
086            if (closeBranch) {
087                branch.close();
088            }
089        }
090    }
091
092    /**
093     * Reads a single character from the proxied reader and writes it to the associated writer.
094     *
095     * @return next character from the reader, or -1 if the reader has ended
096     * @throws IOException if the reader could not be read (or written)
097     */
098    @Override
099    public int read() throws IOException {
100        final int ch = super.read();
101        if (ch != EOF) {
102            branch.write(ch);
103        }
104        return ch;
105    }
106
107    /**
108     * Reads characters from the proxied reader and writes the read characters to the associated writer.
109     *
110     * @param chr character buffer
111     * @return number of characters read, or -1 if the reader has ended
112     * @throws IOException if the reader could not be read (or written)
113     */
114    @Override
115    public int read(final char[] chr) throws IOException {
116        final int n = super.read(chr);
117        if (n != EOF) {
118            branch.write(chr, 0, n);
119        }
120        return n;
121    }
122
123    /**
124     * Reads characters from the proxied reader and writes the read characters to the associated writer.
125     *
126     * @param chr character buffer
127     * @param st  start offset within the buffer
128     * @param end maximum number of characters to read
129     * @return number of characters read, or -1 if the reader has ended
130     * @throws IOException if the reader could not be read (or written)
131     */
132    @Override
133    public int read(final char[] chr, final int st, final int end) throws IOException {
134        final int n = super.read(chr, st, end);
135        if (n != EOF) {
136            branch.write(chr, st, n);
137        }
138        return n;
139    }
140
141    /**
142     * Reads characters from the proxied reader and writes the read characters to the associated writer.
143     *
144     * @param target character buffer
145     * @return number of characters read, or -1 if the reader has ended
146     * @throws IOException if the reader could not be read (or written)
147     */
148    @Override
149    public int read(final CharBuffer target) throws IOException {
150        final int originalPosition = target.position();
151        final int n = super.read(target);
152        if (n != EOF) {
153            // Appending can only be done after resetting the CharBuffer to the
154            // right position and limit.
155            final int newPosition = target.position();
156            final int newLimit = target.limit();
157            try {
158                target.position(originalPosition).limit(newPosition);
159                branch.append(target);
160            } finally {
161                // Reset the CharBuffer as if the appending never happened.
162                target.position(newPosition).limit(newLimit);
163            }
164        }
165        return n;
166    }
167
168}