    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
    026/**
    027 * A reader that imposes a limit to the number of characters that can be read from an underlying reader, returning EOF
    028 * when this limit is reached, regardless of state of underlying reader.
    029 *
    030 * <p>
    031 * One use case is to avoid overrunning the readAheadLimit supplied to {@link Reader#mark(int)}, since reading
    032 * too many characters removes the ability to do a successful reset.
    033 * </p>
    034 *
    035 * @since 2.5
    036 */
    037public class BoundedReader extends Reader {
    038
    039    private static final int INVALID = -1;
    040
    041    private final Reader target;
    042
    043    private int charsRead;
    044
    045    private int markedAt = INVALID;
    046
    047    private int readAheadLimit; // Internally, this value will never exceed the allowed size
    048
    049    private final int maxCharsFromTargetReader;
    050
    051    /**
    052     * Constructs a bounded reader
    053     *
    054     * @param target                   The target stream that will be used
    055     * @param maxCharsFromTargetReader The maximum number of characters that can be read from target
    056     */
    057    public BoundedReader(final Reader target, final int maxCharsFromTargetReader) {
    058        this.target = target;
    059        this.maxCharsFromTargetReader = maxCharsFromTargetReader;
    060    }
    061
    062    /**
    063     * Closes the target
    064     *
    065     * @throws IOException If an I/O error occurs while calling the underlying reader's close method
    066     */
    067    @Override
    068    public void close() throws IOException {
    069        target.close();
    070    }
    071
    072    /**
    073     * marks the target stream
    074     *
    075     * @param readAheadLimit The number of characters that can be read while still retaining the ability to do #reset().
    076     *                       Note that this parameter is not validated with respect to maxCharsFromTargetReader. There
    077     *                       is no way to pass past maxCharsFromTargetReader, even if this value is greater.
    078     *
    079     * @throws IOException If an I/O error occurs while calling the underlying reader's mark method
    080     * @see java.io.Reader#mark(int)
    081     */
    082    @Override
    083    public void mark(final int readAheadLimit) throws IOException {
    084        this.readAheadLimit = readAheadLimit - charsRead;
    085
    086        markedAt = charsRead;
    087
    088        target.mark(readAheadLimit);
    089    }
    090
    091    /**
    092     * Reads a single character
    093     *
    094     * @return -1 on EOF or the character read
    095     * @throws IOException If an I/O error occurs while calling the underlying reader's read method
    096     * @see java.io.Reader#read()
    097     */
    098    @Override
    099    public int read() throws IOException {
    100
    101        if (charsRead >= maxCharsFromTargetReader) {
    102            return EOF;
    103        }
    104
    105        if (markedAt >= 0 && charsRead - markedAt >= readAheadLimit) {
    106            return EOF;
    107        }
    108        charsRead++;
    109        return target.read();
    110    }
    111
    112    /**
    113     * Reads into an array
    114     *
    115     * @param cbuf The buffer to fill
    116     * @param off  The offset
    117     * @param len  The number of chars to read
    118     * @return the number of chars read
    119     * @throws IOException If an I/O error occurs while calling the underlying reader's read method
    120     * @see java.io.Reader#read(char[], int, int)
    121     */
    122    @Override
    123    public int read(final char[] cbuf, final int off, final int len) throws IOException {
    124        int c;
    125        for (int i = 0; i < len; i++) {
    126            c = read();
    127            if (c == EOF) {
    128                return i == 0 ? EOF : i;
    129            }
    130            cbuf[off + i] = (char) c;
    131        }
    132        return len;
    133    }
    134
    135    /**
    136     * Resets the target to the latest mark,
    137     *
    138     * @throws IOException If an I/O error occurs while calling the underlying reader's reset method
    139     * @see java.io.Reader#reset()
    140     */
    141    @Override
    142    public void reset() throws IOException {
    143        charsRead = markedAt;
    144        target.reset();
    145    }
    146}