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 * https://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 026import org.apache.commons.io.IOUtils; 027 028/** 029 * A reader that imposes a limit to the number of characters that can be read from an underlying reader, returning EOF 030 * when this limit is reached, regardless of state of underlying reader. 031 * 032 * <p> 033 * One use case is to avoid overrunning the readAheadLimit supplied to {@link Reader#mark(int)}, since reading 034 * too many characters removes the ability to do a successful reset. 035 * </p> 036 * 037 * @since 2.5 038 */ 039public class BoundedReader extends ProxyReader { 040 041 private static final int INVALID = -1; 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 super(target); 059 this.maxCharsFromTargetReader = maxCharsFromTargetReader; 060 } 061 062 /** 063 * marks the target stream 064 * 065 * @param readAheadLimit The number of characters that can be read while still retaining the ability to do #reset(). 066 * Note that this parameter is not validated with respect to maxCharsFromTargetReader. There 067 * is no way to pass past maxCharsFromTargetReader, even if this value is greater. 068 * 069 * @throws IOException If an I/O error occurs while calling the underlying reader's mark method. 070 * @see Reader#mark(int) 071 */ 072 @Override 073 public void mark(final int readAheadLimit) throws IOException { 074 this.readAheadLimit = readAheadLimit - charsRead; 075 markedAt = charsRead; 076 super.mark(readAheadLimit); 077 } 078 079 /** 080 * Reads a single character 081 * 082 * @return -1 on EOF or the character read. 083 * @throws IOException If an I/O error occurs while calling the underlying reader's read method. 084 * @see Reader#read() 085 */ 086 @Override 087 public int read() throws IOException { 088 if (charsRead >= maxCharsFromTargetReader || markedAt >= 0 && charsRead - markedAt >= readAheadLimit) { 089 return EOF; 090 } 091 charsRead++; 092 return super.read(); 093 } 094 095 /** 096 * Reads into an array 097 * 098 * @param cbuf The buffer to fill. 099 * @param off The offset. 100 * @param len The number of chars to read. 101 * @return the number of chars read. 102 * @throws NullPointerException if the buffer is {@code null}. 103 * @throws IndexOutOfBoundsException if {@code off} or {@code len} are negative, or if {@code off + len} is greater than {@code cbuf.length}. 104 * @throws IOException If an I/O error occurs while calling the underlying reader's read method. 105 * @see Reader#read(char[], int, int) 106 */ 107 @Override 108 public int read(final char[] cbuf, final int off, final int len) throws IOException { 109 IOUtils.checkFromIndexSize(cbuf, off, len); 110 int c; 111 for (int i = 0; i < len; i++) { 112 c = read(); 113 if (c == EOF) { 114 return i == 0 ? EOF : i; 115 } 116 cbuf[off + i] = (char) c; 117 } 118 return len; 119 } 120 121 /** 122 * Resets the target to the latest mark, 123 * 124 * @throws IOException If an I/O error occurs while calling the underlying reader's reset method. 125 * @see Reader#reset() 126 */ 127 @Override 128 public void reset() throws IOException { 129 charsRead = markedAt; 130 super.reset(); 131 } 132 133 @Override 134 public long skip(final long n) throws IOException { 135 charsRead += n; 136 return super.skip(n); 137 } 138}