001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.commons.io.input; 018 019 import java.io.IOException; 020 import java.io.InputStream; 021 022 /** 023 * This is a stream that will only supply bytes up to a certain length - if its 024 * position goes above that, it will stop. 025 * <p> 026 * This is useful to wrap ServletInputStreams. The ServletInputStream will block 027 * if you try to read content from it that isn't there, because it doesn't know 028 * whether the content hasn't arrived yet or whether the content has finished. 029 * So, one of these, initialized with the Content-length sent in the 030 * ServletInputStream's header, will stop it blocking, providing it's been sent 031 * with a correct content length. 032 * 033 * @version $Id: BoundedInputStream.java 1304052 2012-03-22 20:55:29Z ggregory $ 034 * @since 2.0 035 */ 036 public class BoundedInputStream extends InputStream { 037 038 /** the wrapped input stream */ 039 private final InputStream in; 040 041 /** the max length to provide */ 042 private final long max; 043 044 /** the number of bytes already returned */ 045 private long pos = 0; 046 047 /** the marked position */ 048 private long mark = -1; 049 050 /** flag if close shoud be propagated */ 051 private boolean propagateClose = true; 052 053 /** 054 * Creates a new <code>BoundedInputStream</code> that wraps the given input 055 * stream and limits it to a certain size. 056 * 057 * @param in The wrapped input stream 058 * @param size The maximum number of bytes to return 059 */ 060 public BoundedInputStream(InputStream in, long size) { 061 // Some badly designed methods - eg the servlet API - overload length 062 // such that "-1" means stream finished 063 this.max = size; 064 this.in = in; 065 } 066 067 /** 068 * Creates a new <code>BoundedInputStream</code> that wraps the given input 069 * stream and is unlimited. 070 * 071 * @param in The wrapped input stream 072 */ 073 public BoundedInputStream(InputStream in) { 074 this(in, -1); 075 } 076 077 /** 078 * Invokes the delegate's <code>read()</code> method if 079 * the current position is less than the limit. 080 * @return the byte read or -1 if the end of stream or 081 * the limit has been reached. 082 * @throws IOException if an I/O error occurs 083 */ 084 @Override 085 public int read() throws IOException { 086 if (max >= 0 && pos >= max) { 087 return -1; 088 } 089 int result = in.read(); 090 pos++; 091 return result; 092 } 093 094 /** 095 * Invokes the delegate's <code>read(byte[])</code> method. 096 * @param b the buffer to read the bytes into 097 * @return the number of bytes read or -1 if the end of stream or 098 * the limit has been reached. 099 * @throws IOException if an I/O error occurs 100 */ 101 @Override 102 public int read(byte[] b) throws IOException { 103 return this.read(b, 0, b.length); 104 } 105 106 /** 107 * Invokes the delegate's <code>read(byte[], int, int)</code> method. 108 * @param b the buffer to read the bytes into 109 * @param off The start offset 110 * @param len The number of bytes to read 111 * @return the number of bytes read or -1 if the end of stream or 112 * the limit has been reached. 113 * @throws IOException if an I/O error occurs 114 */ 115 @Override 116 public int read(byte[] b, int off, int len) throws IOException { 117 if (max>=0 && pos>=max) { 118 return -1; 119 } 120 long maxRead = max>=0 ? Math.min(len, max-pos) : len; 121 int bytesRead = in.read(b, off, (int)maxRead); 122 123 if (bytesRead==-1) { 124 return -1; 125 } 126 127 pos+=bytesRead; 128 return bytesRead; 129 } 130 131 /** 132 * Invokes the delegate's <code>skip(long)</code> method. 133 * @param n the number of bytes to skip 134 * @return the actual number of bytes skipped 135 * @throws IOException if an I/O error occurs 136 */ 137 @Override 138 public long skip(long n) throws IOException { 139 long toSkip = max>=0 ? Math.min(n, max-pos) : n; 140 long skippedBytes = in.skip(toSkip); 141 pos+=skippedBytes; 142 return skippedBytes; 143 } 144 145 /** 146 * {@inheritDoc} 147 */ 148 @Override 149 public int available() throws IOException { 150 if (max>=0 && pos>=max) { 151 return 0; 152 } 153 return in.available(); 154 } 155 156 /** 157 * Invokes the delegate's <code>toString()</code> method. 158 * @return the delegate's <code>toString()</code> 159 */ 160 @Override 161 public String toString() { 162 return in.toString(); 163 } 164 165 /** 166 * Invokes the delegate's <code>close()</code> method 167 * if {@link #isPropagateClose()} is <code>true</code>. 168 * @throws IOException if an I/O error occurs 169 */ 170 @Override 171 public void close() throws IOException { 172 if (propagateClose) { 173 in.close(); 174 } 175 } 176 177 /** 178 * Invokes the delegate's <code>reset()</code> method. 179 * @throws IOException if an I/O error occurs 180 */ 181 @Override 182 public synchronized void reset() throws IOException { 183 in.reset(); 184 pos = mark; 185 } 186 187 /** 188 * Invokes the delegate's <code>mark(int)</code> method. 189 * @param readlimit read ahead limit 190 */ 191 @Override 192 public synchronized void mark(int readlimit) { 193 in.mark(readlimit); 194 mark = pos; 195 } 196 197 /** 198 * Invokes the delegate's <code>markSupported()</code> method. 199 * @return true if mark is supported, otherwise false 200 */ 201 @Override 202 public boolean markSupported() { 203 return in.markSupported(); 204 } 205 206 /** 207 * Indicates whether the {@link #close()} method 208 * should propagate to the underling {@link InputStream}. 209 * 210 * @return <code>true</code> if calling {@link #close()} 211 * propagates to the <code>close()</code> method of the 212 * underlying stream or <code>false</code> if it does not. 213 */ 214 public boolean isPropagateClose() { 215 return propagateClose; 216 } 217 218 /** 219 * Set whether the {@link #close()} method 220 * should propagate to the underling {@link InputStream}. 221 * 222 * @param propagateClose <code>true</code> if calling 223 * {@link #close()} propagates to the <code>close()</code> 224 * method of the underlying stream or 225 * <code>false</code> if it does not. 226 */ 227 public void setPropagateClose(boolean propagateClose) { 228 this.propagateClose = propagateClose; 229 } 230 }