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 Reader { 040 041 private static final int INVALID = -1; 042 043 private final Reader target; 044 045 private int charsRead; 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 */ 059 public BoundedReader(final Reader target, final int maxCharsFromTargetReader) { 060 this.target = target; 061 this.maxCharsFromTargetReader = maxCharsFromTargetReader; 062 } 063 064 /** 065 * Closes the target 066 * 067 * @throws IOException If an I/O error occurs while calling the underlying reader's close method 068 */ 069 @Override 070 public void close() throws IOException { 071 target.close(); 072 } 073 074 /** 075 * marks the target stream 076 * 077 * @param readAheadLimit The number of characters that can be read while still retaining the ability to do #reset(). 078 * Note that this parameter is not validated with respect to maxCharsFromTargetReader. There 079 * is no way to pass past maxCharsFromTargetReader, even if this value is greater. 080 * 081 * @throws IOException If an I/O error occurs while calling the underlying reader's mark method 082 * @see Reader#mark(int) 083 */ 084 @Override 085 public void mark(final int readAheadLimit) throws IOException { 086 this.readAheadLimit = readAheadLimit - charsRead; 087 088 markedAt = charsRead; 089 090 target.mark(readAheadLimit); 091 } 092 093 /** 094 * Reads a single character 095 * 096 * @return -1 on EOF or the character read 097 * @throws IOException If an I/O error occurs while calling the underlying reader's read method 098 * @see Reader#read() 099 */ 100 @Override 101 public int read() throws IOException { 102 103 if (charsRead >= maxCharsFromTargetReader) { 104 return EOF; 105 } 106 107 if (markedAt >= 0 && charsRead - markedAt >= readAheadLimit) { 108 return EOF; 109 } 110 charsRead++; 111 return target.read(); 112 } 113 114 /** 115 * Reads into an array 116 * 117 * @param cbuf The buffer to fill 118 * @param off The offset 119 * @param len The number of chars to read 120 * @return the number of chars read 121 * @throws NullPointerException if the buffer is {@code null}. 122 * @throws IndexOutOfBoundsException if {@code off} or {@code len} are negative, or if {@code off + len} is greater than {@code cbuf.length}. 123 * @throws IOException If an I/O error occurs while calling the underlying reader's read method 124 * @see Reader#read(char[], int, int) 125 */ 126 @Override 127 public int read(final char[] cbuf, final int off, final int len) throws IOException { 128 IOUtils.checkFromIndexSize(cbuf, off, len); 129 int c; 130 for (int i = 0; i < len; i++) { 131 c = read(); 132 if (c == EOF) { 133 return i == 0 ? EOF : i; 134 } 135 cbuf[off + i] = (char) c; 136 } 137 return len; 138 } 139 140 /** 141 * Resets the target to the latest mark, 142 * 143 * @throws IOException If an I/O error occurs while calling the underlying reader's reset method 144 * @see Reader#reset() 145 */ 146 @Override 147 public void reset() throws IOException { 148 charsRead = markedAt; 149 target.reset(); 150 } 151}