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