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 }