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