    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 *    http://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 java.io.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     * @throws IOException Never thrown.
    057     */
    058    public BoundedReader(final Reader target, final int maxCharsFromTargetReader) throws IOException {
    059        this.target = target;
    060        this.maxCharsFromTargetReader = maxCharsFromTargetReader;
    061    }
    062
    063    /**
    064     * Closes the target
    065     *
    066     * @throws IOException If an I/O error occurs while calling the underlying reader's close method
    067     */
    068    @Override
    069    public void close() throws IOException {
    070        target.close();
    071    }
    072
    073    /**
    074     * Resets the target to the latest mark,
    075     *
    076     * @throws IOException If an I/O error occurs while calling the underlying reader's reset method
    077     * @see java.io.Reader#reset()
    078     */
    079    @Override
    080    public void reset() throws IOException {
    081        charsRead = markedAt;
    082        target.reset();
    083    }
    084
    085    /**
    086     * marks the target stream
    087     *
    088     * @param readAheadLimit The number of characters that can be read while still retaining the ability to do #reset().
    089     *                       Note that this parameter is not validated with respect to maxCharsFromTargetReader. There
    090     *                       is no way to pass past maxCharsFromTargetReader, even if this value is greater.
    091     *
    092     * @throws IOException If an I/O error occurs while calling the underlying reader's mark method
    093     * @see java.io.Reader#mark(int)
    094     */
    095    @Override
    096    public void mark(final int readAheadLimit) throws IOException {
    097        this.readAheadLimit = readAheadLimit - charsRead;
    098
    099        markedAt = charsRead;
    100
    101        target.mark(readAheadLimit);
    102    }
    103
    104    /**
    105     * Reads a single character
    106     *
    107     * @return -1 on EOF or the character read
    108     * @throws IOException If an I/O error occurs while calling the underlying reader's read method
    109     * @see java.io.Reader#read()
    110     */
    111    @Override
    112    public int read() throws IOException {
    113
    114        if (charsRead >= maxCharsFromTargetReader) {
    115            return EOF;
    116        }
    117
    118        if (markedAt >= 0 && (charsRead - markedAt) >= readAheadLimit) {
    119            return EOF;
    120        }
    121        charsRead++;
    122        return target.read();
    123    }
    124
    125    /**
    126     * Reads into an array
    127     *
    128     * @param cbuf The buffer to fill
    129     * @param off  The offset
    130     * @param len  The number of chars to read
    131     * @return the number of chars read
    132     * @throws IOException If an I/O error occurs while calling the underlying reader's read method
    133     * @see java.io.Reader#read(char[], int, int)
    134     */
    135    @Override
    136    public int read(final char[] cbuf, final int off, final int len) throws IOException {
    137        int c;
    138        for (int i = 0; i < len; i++) {
    139            c = read();
    140            if (c == EOF) {
    141                return i == 0 ? EOF : i;
    142            }
    143            cbuf[off + i] = (char) c;
    144        }
    145        return len;
    146    }
    147}