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   *    https://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  import org.apache.commons.io.IOUtils;
27  
28  /**
29   * A reader that imposes a limit to the number of characters that can be read from an underlying reader, returning EOF
30   * when this limit is reached, regardless of state of underlying reader.
31   *
32   * <p>
33   * One use case is to avoid overrunning the readAheadLimit supplied to {@link Reader#mark(int)}, since reading
34   * too many characters removes the ability to do a successful reset.
35   * </p>
36   *
37   * @since 2.5
38   */
39  public class BoundedReader extends Reader {
40  
41      private static final int INVALID = -1;
42  
43      private final Reader target;
44  
45      private int charsRead;
46  
47      private int markedAt = INVALID;
48  
49      private int readAheadLimit; // Internally, this value will never exceed the allowed size
50  
51      private final int maxCharsFromTargetReader;
52  
53      /**
54       * Constructs a bounded reader
55       *
56       * @param target                   The target stream that will be used
57       * @param maxCharsFromTargetReader The maximum number of characters that can be read from target
58       */
59      public BoundedReader(final Reader target, final int maxCharsFromTargetReader) {
60          this.target = target;
61          this.maxCharsFromTargetReader = maxCharsFromTargetReader;
62      }
63  
64      /**
65       * Closes the target
66       *
67       * @throws IOException If an I/O error occurs while calling the underlying reader's close method
68       */
69      @Override
70      public void close() throws IOException {
71          target.close();
72      }
73  
74      /**
75       * marks the target stream
76       *
77       * @param readAheadLimit The number of characters that can be read while still retaining the ability to do #reset().
78       *                       Note that this parameter is not validated with respect to maxCharsFromTargetReader. There
79       *                       is no way to pass past maxCharsFromTargetReader, even if this value is greater.
80       *
81       * @throws IOException If an I/O error occurs while calling the underlying reader's mark method
82       * @see Reader#mark(int)
83       */
84      @Override
85      public void mark(final int readAheadLimit) throws IOException {
86          this.readAheadLimit = readAheadLimit - charsRead;
87  
88          markedAt = charsRead;
89  
90          target.mark(readAheadLimit);
91      }
92  
93      /**
94       * Reads a single character
95       *
96       * @return -1 on EOF or the character read
97       * @throws IOException If an I/O error occurs while calling the underlying reader's read method
98       * @see Reader#read()
99       */
100     @Override
101     public int read() throws IOException {
102 
103         if (charsRead >= maxCharsFromTargetReader) {
104             return EOF;
105         }
106 
107         if (markedAt >= 0 && charsRead - markedAt >= readAheadLimit) {
108             return EOF;
109         }
110         charsRead++;
111         return target.read();
112     }
113 
114     /**
115      * Reads into an array
116      *
117      * @param cbuf The buffer to fill
118      * @param off  The offset
119      * @param len  The number of chars to read
120      * @return the number of chars read
121      * @throws NullPointerException if the buffer is {@code null}.
122      * @throws IndexOutOfBoundsException if {@code off} or {@code len} are negative, or if {@code off + len} is greater than {@code cbuf.length}.
123      * @throws IOException If an I/O error occurs while calling the underlying reader's read method
124      * @see Reader#read(char[], int, int)
125      */
126     @Override
127     public int read(final char[] cbuf, final int off, final int len) throws IOException {
128         IOUtils.checkFromIndexSize(cbuf, off, len);
129         int c;
130         for (int i = 0; i < len; i++) {
131             c = read();
132             if (c == EOF) {
133                 return i == 0 ? EOF : i;
134             }
135             cbuf[off + i] = (char) c;
136         }
137         return len;
138     }
139 
140     /**
141      * Resets the target to the latest mark,
142      *
143      * @throws IOException If an I/O error occurs while calling the underlying reader's reset method
144      * @see Reader#reset()
145      */
146     @Override
147     public void reset() throws IOException {
148         charsRead = markedAt;
149         target.reset();
150     }
151 }