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 }