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}