View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.io.input;
20  
21  import static org.apache.commons.io.IOUtils.EOF;
22  
23  import java.io.IOException;
24  import java.io.Reader;
25  
26  /**
27   * A reader that imposes a limit to the number of characters that can be read from an underlying reader, returning EOF
28   * when this limit is reached, regardless of state of underlying reader.
29   *
30   * <p>
31   * One use case is to avoid overrunning the readAheadLimit supplied to {@link java.io.Reader#mark(int)}, since reading
32   * too many characters removes the ability to do a successful reset.
33   * </p>
34   *
35   * @since 2.5
36   */
37  public class BoundedReader extends Reader {
38  
39      private static final int INVALID = -1;
40  
41      private final Reader target;
42  
43      private int charsRead;
44  
45      private int markedAt = INVALID;
46  
47      private int readAheadLimit; // Internally, this value will never exceed the allowed size
48  
49      private final int maxCharsFromTargetReader;
50  
51      /**
52       * Constructs a bounded reader
53       *
54       * @param target                   The target stream that will be used
55       * @param maxCharsFromTargetReader The maximum number of characters that can be read from target
56       */
57      public BoundedReader(final Reader target, final int maxCharsFromTargetReader) {
58          this.target = target;
59          this.maxCharsFromTargetReader = maxCharsFromTargetReader;
60      }
61  
62      /**
63       * Closes the target
64       *
65       * @throws IOException If an I/O error occurs while calling the underlying reader's close method
66       */
67      @Override
68      public void close() throws IOException {
69          target.close();
70      }
71  
72      /**
73       * marks the target stream
74       *
75       * @param readAheadLimit The number of characters that can be read while still retaining the ability to do #reset().
76       *                       Note that this parameter is not validated with respect to maxCharsFromTargetReader. There
77       *                       is no way to pass past maxCharsFromTargetReader, even if this value is greater.
78       *
79       * @throws IOException If an I/O error occurs while calling the underlying reader's mark method
80       * @see java.io.Reader#mark(int)
81       */
82      @Override
83      public void mark(final int readAheadLimit) throws IOException {
84          this.readAheadLimit = readAheadLimit - charsRead;
85  
86          markedAt = charsRead;
87  
88          target.mark(readAheadLimit);
89      }
90  
91      /**
92       * Reads a single character
93       *
94       * @return -1 on EOF or the character read
95       * @throws IOException If an I/O error occurs while calling the underlying reader's read method
96       * @see java.io.Reader#read()
97       */
98      @Override
99      public int read() throws IOException {
100 
101         if (charsRead >= maxCharsFromTargetReader) {
102             return EOF;
103         }
104 
105         if (markedAt >= 0 && charsRead - markedAt >= readAheadLimit) {
106             return EOF;
107         }
108         charsRead++;
109         return target.read();
110     }
111 
112     /**
113      * Reads into an array
114      *
115      * @param cbuf The buffer to fill
116      * @param off  The offset
117      * @param len  The number of chars to read
118      * @return the number of chars read
119      * @throws IOException If an I/O error occurs while calling the underlying reader's read method
120      * @see java.io.Reader#read(char[], int, int)
121      */
122     @Override
123     public int read(final char[] cbuf, final int off, final int len) throws IOException {
124         int c;
125         for (int i = 0; i < len; i++) {
126             c = read();
127             if (c == EOF) {
128                 return i == 0 ? EOF : i;
129             }
130             cbuf[off + i] = (char) c;
131         }
132         return len;
133     }
134 
135     /**
136      * Resets the target to the latest mark,
137      *
138      * @throws IOException If an I/O error occurs while calling the underlying reader's reset method
139      * @see java.io.Reader#reset()
140      */
141     @Override
142     public void reset() throws IOException {
143         charsRead = markedAt;
144         target.reset();
145     }
146 }