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