1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.io.input;
18
19 import static org.apache.commons.io.IOUtils.EOF;
20
21 import java.io.IOException;
22 import java.io.Reader;
23 import java.io.Writer;
24 import java.nio.CharBuffer;
25
26 /**
27 * Reader proxy that transparently writes a copy of all characters read from the proxied reader to a given Reader. Using
28 * {@link #skip(long)} or {@link #mark(int)}/{@link #reset()} on the reader will result on some characters from the
29 * reader being skipped or duplicated in the writer.
30 * <p>
31 * The proxied reader is closed when the {@link #close()} method is called on this proxy. You may configure whether the
32 * reader closes the writer.
33 * </p>
34 *
35 * @since 2.7
36 */
37 public class TeeReader extends ProxyReader {
38
39 /**
40 * The writer that will receive a copy of all characters read from the proxied reader.
41 */
42 private final Writer branch;
43
44 /**
45 * Flag for closing the associated writer when this reader is closed.
46 */
47 private final boolean closeBranch;
48
49 /**
50 * Constructs a TeeReader that proxies the given {@link Reader} and copies all read characters to the given
51 * {@link Writer}. The given writer will not be closed when this reader gets closed.
52 *
53 * @param input reader to be proxied
54 * @param branch writer that will receive a copy of all characters read
55 */
56 public TeeReader(final Reader input, final Writer branch) {
57 this(input, branch, false);
58 }
59
60 /**
61 * Constructs a TeeReader that proxies the given {@link Reader} and copies all read characters to the given
62 * {@link Writer}. The given writer will be closed when this reader gets closed if the closeBranch parameter is
63 * {@code true}.
64 *
65 * @param input reader to be proxied
66 * @param branch writer that will receive a copy of all characters read
67 * @param closeBranch flag for closing also the writer when this reader is closed
68 */
69 public TeeReader(final Reader input, final Writer branch, final boolean closeBranch) {
70 super(input);
71 this.branch = branch;
72 this.closeBranch = closeBranch;
73 }
74
75 /**
76 * Closes the proxied reader and, if so configured, the associated writer. An exception thrown from the reader will
77 * not prevent closing of the writer.
78 *
79 * @throws IOException if either the reader or writer could not be closed
80 */
81 @Override
82 public void close() throws IOException {
83 try {
84 super.close();
85 } finally {
86 if (closeBranch) {
87 branch.close();
88 }
89 }
90 }
91
92 /**
93 * Reads a single character from the proxied reader and writes it to the associated writer.
94 *
95 * @return next character from the reader, or -1 if the reader has ended
96 * @throws IOException if the reader could not be read (or written)
97 */
98 @Override
99 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 }