001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *    https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.commons.io.input;
020
021import static org.apache.commons.io.IOUtils.EOF;
022
023import java.io.IOException;
024import java.io.Reader;
025
026import org.apache.commons.io.IOUtils;
027
028/**
029 * A reader that imposes a limit to the number of characters that can be read from an underlying reader, returning EOF
030 * when this limit is reached, regardless of state of underlying reader.
031 *
032 * <p>
033 * One use case is to avoid overrunning the readAheadLimit supplied to {@link Reader#mark(int)}, since reading
034 * too many characters removes the ability to do a successful reset.
035 * </p>
036 *
037 * @since 2.5
038 */
039public class BoundedReader extends Reader {
040
041    private static final int INVALID = -1;
042
043    private final Reader target;
044
045    private int charsRead;
046
047    private int markedAt = INVALID;
048
049    private int readAheadLimit; // Internally, this value will never exceed the allowed size
050
051    private final int maxCharsFromTargetReader;
052
053    /**
054     * Constructs a bounded reader
055     *
056     * @param target                   The target stream that will be used
057     * @param maxCharsFromTargetReader The maximum number of characters that can be read from target
058     */
059    public BoundedReader(final Reader target, final int maxCharsFromTargetReader) {
060        this.target = target;
061        this.maxCharsFromTargetReader = maxCharsFromTargetReader;
062    }
063
064    /**
065     * Closes the target
066     *
067     * @throws IOException If an I/O error occurs while calling the underlying reader's close method
068     */
069    @Override
070    public void close() throws IOException {
071        target.close();
072    }
073
074    /**
075     * marks the target stream
076     *
077     * @param readAheadLimit The number of characters that can be read while still retaining the ability to do #reset().
078     *                       Note that this parameter is not validated with respect to maxCharsFromTargetReader. There
079     *                       is no way to pass past maxCharsFromTargetReader, even if this value is greater.
080     *
081     * @throws IOException If an I/O error occurs while calling the underlying reader's mark method
082     * @see Reader#mark(int)
083     */
084    @Override
085    public void mark(final int readAheadLimit) throws IOException {
086        this.readAheadLimit = readAheadLimit - charsRead;
087
088        markedAt = charsRead;
089
090        target.mark(readAheadLimit);
091    }
092
093    /**
094     * Reads a single character
095     *
096     * @return -1 on EOF or the character read
097     * @throws IOException If an I/O error occurs while calling the underlying reader's read method
098     * @see Reader#read()
099     */
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}