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 * http://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 }