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 018package org.apache.commons.io.input; 019 020import java.io.BufferedInputStream; 021import java.io.IOException; 022import java.io.InputStream; 023 024import org.apache.commons.io.IOUtils; 025import org.apache.commons.io.build.AbstractStreamBuilder; 026 027/** 028 * An unsynchronized version of {@link BufferedInputStream}, not thread-safe. 029 * <p> 030 * Wraps an existing {@link InputStream} and <em>buffers</em> the input. Expensive interaction with the underlying input stream is minimized, since most 031 * (smaller) requests can be satisfied by accessing the buffer alone. The drawback is that some extra space is required to hold the buffer and that copying 032 * takes place when filling that buffer, but this is usually outweighed by the performance benefits. 033 * </p> 034 * <p> 035 * A typical application pattern for the class looks like this: 036 * </p> 037 * 038 * <pre> 039 * UnsynchronizedBufferedInputStream buf = new UnsynchronizedBufferedInputStream(new FileInputStream("file.java")); 040 * </pre> 041 * <p> 042 * Provenance: Apache Harmony and modified. 043 * </p> 044 * 045 * @see BufferedInputStream 046 * @since 2.12.0 047 */ 048//@NotThreadSafe 049public final class UnsynchronizedBufferedInputStream extends UnsynchronizedFilterInputStream { 050 051 /** 052 * Builds a new {@link UnsynchronizedBufferedInputStream} instance. 053 * <p> 054 * Using File IO: 055 * </p> 056 * <pre>{@code 057 * UnsynchronizedBufferedInputStream s = UnsynchronizedBufferedInputStream.builder() 058 * .setFile(file) 059 * .setBufferSize(8192) 060 * .get()} 061 * </pre> 062 * <p> 063 * Using NIO Path: 064 * </p> 065 * <pre>{@code 066 * UnsynchronizedBufferedInputStream s = UnsynchronizedBufferedInputStream.builder() 067 * .setPath(path) 068 * .setBufferSize(8192) 069 * .get()} 070 * </pre> 071 */ 072 public static class Builder extends AbstractStreamBuilder<UnsynchronizedBufferedInputStream, Builder> { 073 074 /** 075 * Constructs a new instance. 076 * 077 * @throws UnsupportedOperationException if the origin cannot be converted to an InputStream. 078 */ 079 @SuppressWarnings("resource") // Caller closes. 080 @Override 081 public UnsynchronizedBufferedInputStream get() throws IOException { 082 return new UnsynchronizedBufferedInputStream(getOrigin().getInputStream(), getBufferSize()); 083 } 084 085 } 086 087 /** 088 * The buffer containing the current bytes read from the target InputStream. 089 */ 090 protected volatile byte[] buffer; 091 092 /** 093 * The total number of bytes inside the byte array {@code buf}. 094 */ 095 protected int count; 096 097 /** 098 * The current limit, which when passed, invalidates the current mark. 099 */ 100 protected int markLimit; 101 102 /** 103 * The currently marked position. -1 indicates no mark has been set or the mark has been invalidated. 104 */ 105 protected int markPos = IOUtils.EOF; 106 107 /** 108 * The current position within the byte array {@code buf}. 109 */ 110 protected int pos; 111 112 /** 113 * Constructs a new {@code BufferedInputStream} on the {@link InputStream} {@code in}. The buffer size is specified by the parameter {@code size} and all 114 * reads are now filtered through this stream. 115 * 116 * @param in the input stream the buffer reads from. 117 * @param size the size of buffer to allocate. 118 * @throws IllegalArgumentException if {@code size < 0}. 119 */ 120 private UnsynchronizedBufferedInputStream(final InputStream in, final int size) { 121 super(in); 122 if (size <= 0) { 123 throw new IllegalArgumentException("Size must be > 0"); 124 } 125 buffer = new byte[size]; 126 } 127 128 /** 129 * Returns the number of bytes that are available before this stream will block. This method returns the number of bytes available in the buffer plus those 130 * available in the source stream. 131 * 132 * @return the number of bytes available before blocking. 133 * @throws IOException if this stream is closed. 134 */ 135 @Override 136 public int available() throws IOException { 137 final InputStream localIn = inputStream; // 'in' could be invalidated by close() 138 if (buffer == null || localIn == null) { 139 throw new IOException("Stream is closed"); 140 } 141 return count - pos + localIn.available(); 142 } 143 144 /** 145 * Closes this stream. The source stream is closed and any resources associated with it are released. 146 * 147 * @throws IOException if an error occurs while closing this stream. 148 */ 149 @Override 150 public void close() throws IOException { 151 buffer = null; 152 final InputStream localIn = inputStream; 153 inputStream = null; 154 if (localIn != null) { 155 localIn.close(); 156 } 157 } 158 159 private int fillBuffer(final InputStream localIn, byte[] localBuf) throws IOException { 160 if (markPos == IOUtils.EOF || pos - markPos >= markLimit) { 161 /* Mark position not set or exceeded readlimit */ 162 final int result = localIn.read(localBuf); 163 if (result > 0) { 164 markPos = IOUtils.EOF; 165 pos = 0; 166 count = result; 167 } 168 return result; 169 } 170 if (markPos == 0 && markLimit > localBuf.length) { 171 /* Increase buffer size to accommodate the readlimit */ 172 int newLength = localBuf.length * 2; 173 if (newLength > markLimit) { 174 newLength = markLimit; 175 } 176 final byte[] newbuf = new byte[newLength]; 177 System.arraycopy(localBuf, 0, newbuf, 0, localBuf.length); 178 // Reassign buf, which will invalidate any local references 179 // FIXME: what if buf was null? 180 localBuf = buffer = newbuf; 181 } else if (markPos > 0) { 182 System.arraycopy(localBuf, markPos, localBuf, 0, localBuf.length - markPos); 183 } 184 /* Set the new position and mark position */ 185 pos -= markPos; 186 count = markPos = 0; 187 final int bytesread = localIn.read(localBuf, pos, localBuf.length - pos); 188 count = bytesread <= 0 ? pos : pos + bytesread; 189 return bytesread; 190 } 191 192 byte[] getBuffer() { 193 return buffer; 194 } 195 196 /** 197 * Sets a mark position in this stream. The parameter {@code readlimit} indicates how many bytes can be read before a mark is invalidated. Calling 198 * {@code reset()} will reposition the stream back to the marked position if {@code readlimit} has not been surpassed. The underlying buffer may be 199 * increased in size to allow {@code readlimit} number of bytes to be supported. 200 * 201 * @param readlimit the number of bytes that can be read before the mark is invalidated. 202 * @see #reset() 203 */ 204 @Override 205 public void mark(final int readlimit) { 206 markLimit = readlimit; 207 markPos = pos; 208 } 209 210 /** 211 * Indicates whether {@code BufferedInputStream} supports the {@code mark()} and {@code reset()} methods. 212 * 213 * @return {@code true} for BufferedInputStreams. 214 * @see #mark(int) 215 * @see #reset() 216 */ 217 @Override 218 public boolean markSupported() { 219 return true; 220 } 221 222 /** 223 * Reads a single byte from this stream and returns it as an integer in the range from 0 to 255. Returns -1 if the end of the source string has been 224 * reached. If the internal buffer does not contain any available bytes then it is filled from the source stream and the first byte is returned. 225 * 226 * @return the byte read or -1 if the end of the source stream has been reached. 227 * @throws IOException if this stream is closed or another IOException occurs. 228 */ 229 @Override 230 public int read() throws IOException { 231 // Use local refs since buf and in may be invalidated by an 232 // unsynchronized close() 233 byte[] localBuf = buffer; 234 final InputStream localIn = inputStream; 235 if (localBuf == null || localIn == null) { 236 throw new IOException("Stream is closed"); 237 } 238 239 /* Are there buffered bytes available? */ 240 if (pos >= count && fillBuffer(localIn, localBuf) == IOUtils.EOF) { 241 return IOUtils.EOF; /* no, fill buffer */ 242 } 243 // localBuf may have been invalidated by fillbuf 244 if (localBuf != buffer) { 245 localBuf = buffer; 246 if (localBuf == null) { 247 throw new IOException("Stream is closed"); 248 } 249 } 250 251 /* Did filling the buffer fail with -1 (EOF)? */ 252 if (count - pos > 0) { 253 return localBuf[pos++] & 0xFF; 254 } 255 return IOUtils.EOF; 256 } 257 258 /** 259 * Reads at most {@code length} bytes from this stream and stores them in byte array {@code buffer} starting at offset {@code offset}. Returns the number of 260 * bytes actually read or -1 if no bytes were read and the end of the stream was encountered. If all the buffered bytes have been used, a mark has not been 261 * set and the requested number of bytes is larger than the receiver's buffer size, this implementation bypasses the buffer and simply places the results 262 * directly into {@code buffer}. 263 * 264 * @param buffer the byte array in which to store the bytes read. 265 * @param offset the initial position in {@code buffer} to store the bytes read from this stream. 266 * @param length the maximum number of bytes to store in {@code buffer}. 267 * @return the number of bytes actually read or -1 if end of stream. 268 * @throws IndexOutOfBoundsException if {@code offset < 0} or {@code length < 0}, or if {@code offset + length} is greater than the size of {@code buffer}. 269 * @throws IOException if the stream is already closed or another IOException occurs. 270 */ 271 @Override 272 public int read(final byte[] buffer, int offset, final int length) throws IOException { 273 // Use local ref since buf may be invalidated by an unsynchronized 274 // close() 275 byte[] localBuf = buffer; 276 if (localBuf == null) { 277 throw new IOException("Stream is closed"); 278 } 279 // avoid int overflow 280 if (offset > buffer.length - length || offset < 0 || length < 0) { 281 throw new IndexOutOfBoundsException(); 282 } 283 if (length == 0) { 284 return 0; 285 } 286 final InputStream localIn = inputStream; 287 if (localIn == null) { 288 throw new IOException("Stream is closed"); 289 } 290 291 int required; 292 if (pos < count) { 293 /* There are bytes available in the buffer. */ 294 final int copylength = count - pos >= length ? length : count - pos; 295 System.arraycopy(localBuf, pos, buffer, offset, copylength); 296 pos += copylength; 297 if (copylength == length || localIn.available() == 0) { 298 return copylength; 299 } 300 offset += copylength; 301 required = length - copylength; 302 } else { 303 required = length; 304 } 305 306 while (true) { 307 final int read; 308 /* 309 * If we're not marked and the required size is greater than the buffer, simply read the bytes directly bypassing the buffer. 310 */ 311 if (markPos == IOUtils.EOF && required >= localBuf.length) { 312 read = localIn.read(buffer, offset, required); 313 if (read == IOUtils.EOF) { 314 return required == length ? IOUtils.EOF : length - required; 315 } 316 } else { 317 if (fillBuffer(localIn, localBuf) == IOUtils.EOF) { 318 return required == length ? IOUtils.EOF : length - required; 319 } 320 // localBuf may have been invalidated by fillBuffer() 321 if (localBuf != buffer) { 322 localBuf = buffer; 323 if (localBuf == null) { 324 throw new IOException("Stream is closed"); 325 } 326 } 327 328 read = count - pos >= required ? required : count - pos; 329 System.arraycopy(localBuf, pos, buffer, offset, read); 330 pos += read; 331 } 332 required -= read; 333 if (required == 0) { 334 return length; 335 } 336 if (localIn.available() == 0) { 337 return length - required; 338 } 339 offset += read; 340 } 341 } 342 343 /** 344 * Resets this stream to the last marked location. 345 * 346 * @throws IOException if this stream is closed, no mark has been set or the mark is no longer valid because more than {@code readlimit} bytes have been 347 * read since setting the mark. 348 * @see #mark(int) 349 */ 350 @Override 351 public void reset() throws IOException { 352 if (buffer == null) { 353 throw new IOException("Stream is closed"); 354 } 355 if (IOUtils.EOF == markPos) { 356 throw new IOException("Mark has been invalidated"); 357 } 358 pos = markPos; 359 } 360 361 /** 362 * Skips {@code amount} number of bytes in this stream. Subsequent {@code read()}'s will not return these bytes unless {@code reset()} is used. 363 * 364 * @param amount the number of bytes to skip. {@code skip} does nothing and returns 0 if {@code amount} is less than zero. 365 * @return the number of bytes actually skipped. 366 * @throws IOException if this stream is closed or another IOException occurs. 367 */ 368 @Override 369 public long skip(final long amount) throws IOException { 370 // Use local refs since buf and in may be invalidated by an 371 // unsynchronized close() 372 final byte[] localBuf = buffer; 373 final InputStream localIn = inputStream; 374 if (localBuf == null) { 375 throw new IOException("Stream is closed"); 376 } 377 if (amount < 1) { 378 return 0; 379 } 380 if (localIn == null) { 381 throw new IOException("Stream is closed"); 382 } 383 384 if (count - pos >= amount) { 385 pos += amount; 386 return amount; 387 } 388 long read = count - pos; 389 pos = count; 390 391 if (markPos != IOUtils.EOF && amount <= markLimit) { 392 if (fillBuffer(localIn, localBuf) == IOUtils.EOF) { 393 return read; 394 } 395 if (count - pos >= amount - read) { 396 pos += amount - read; 397 return amount; 398 } 399 // Couldn't get all the bytes, skip what we read 400 read += count - pos; 401 pos = count; 402 return read; 403 } 404 return read + localIn.skip(amount - read); 405 } 406}