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