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 ProxyReader {
40  
41      private static final int INVALID = -1;
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          super(target);
59          this.maxCharsFromTargetReader = maxCharsFromTargetReader;
60      }
61  
62      /**
63       * marks the target stream
64       *
65       * @param readAheadLimit The number of characters that can be read while still retaining the ability to do #reset().
66       *                       Note that this parameter is not validated with respect to maxCharsFromTargetReader. There
67       *                       is no way to pass past maxCharsFromTargetReader, even if this value is greater.
68       *
69       * @throws IOException If an I/O error occurs while calling the underlying reader's mark method.
70       * @see Reader#mark(int)
71       */
72      @Override
73      public void mark(final int readAheadLimit) throws IOException {
74          this.readAheadLimit = readAheadLimit - charsRead;
75          markedAt = charsRead;
76          super.mark(readAheadLimit);
77      }
78  
79      /**
80       * Reads a single character
81       *
82       * @return -1 on EOF or the character read.
83       * @throws IOException If an I/O error occurs while calling the underlying reader's read method.
84       * @see Reader#read()
85       */
86      @Override
87      public int read() throws IOException {
88          if (charsRead >= maxCharsFromTargetReader || markedAt >= 0 && charsRead - markedAt >= readAheadLimit) {
89              return EOF;
90          }
91          charsRead++;
92          return super.read();
93      }
94  
95      /**
96       * Reads into an array
97       *
98       * @param cbuf The buffer to fill.
99       * @param off  The offset.
100      * @param len  The number of chars to read.
101      * @return the number of chars read.
102      * @throws NullPointerException if the buffer is {@code null}.
103      * @throws IndexOutOfBoundsException if {@code off} or {@code len} are negative, or if {@code off + len} is greater than {@code cbuf.length}.
104      * @throws IOException If an I/O error occurs while calling the underlying reader's read method.
105      * @see Reader#read(char[], int, int)
106      */
107     @Override
108     public int read(final char[] cbuf, final int off, final int len) throws IOException {
109         IOUtils.checkFromIndexSize(cbuf, off, len);
110         int c;
111         for (int i = 0; i < len; i++) {
112             c = read();
113             if (c == EOF) {
114                 return i == 0 ? EOF : i;
115             }
116             cbuf[off + i] = (char) c;
117         }
118         return len;
119     }
120 
121     /**
122      * Resets the target to the latest mark,
123      *
124      * @throws IOException If an I/O error occurs while calling the underlying reader's reset method.
125      * @see Reader#reset()
126      */
127     @Override
128     public void reset() throws IOException {
129         charsRead = markedAt;
130         super.reset();
131     }
132 
133     @Override
134     public long skip(final long n) throws IOException {
135         charsRead += n;
136         return super.skip(n);
137     }
138 }