View Javadoc
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 }