blob: 91e56f7b9ccf1d1b0449dd191ad28fd6b140f873 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.io.input;
import static org.apache.commons.io.IOUtils.EOF;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.build.AbstractStreamBuilder;
//@formatter:off
/**
* Reads bytes up to a maximum count and stops once reached.
* <p>
* To build an instance, see {@link AbstractBuilder}.
* </p>
* <p>
* By default, a {@link BoundedInputStream} is <em>unbound</em>; so make sure to call {@link AbstractBuilder#setMaxCount(long)}.
* </p>
* <p>
* You can find out how many bytes this stream has seen so far by calling {@link BoundedInputStream#getCount()}. This value reflects bytes read and skipped.
* </p>
* <h2>Using a ServletInputStream</h2>
* <p>
* A {@code ServletInputStream} can block if you try to read content that isn't there
* because it doesn't know whether the content hasn't arrived yet or whether the content has finished. Initialize an {@link BoundedInputStream} with the
* {@code Content-Length} sent in the {@code ServletInputStream}'s header, this stop it from blocking, providing it's been sent with a correct content
* length in the first place.
* </p>
* <h2>Using NIO</h2>
* <pre>{@code
* BoundedInputStream s = BoundedInputStream.builder()
* .setPath(Paths.get("MyFile.xml"))
* .setMaxCount(1024)
* .setPropagateClose(false)
* .get();
* }
* </pre>
* <h2>Using IO</h2>
* <pre>{@code
* BoundedInputStream s = BoundedInputStream.builder()
* .setFile(new File("MyFile.xml"))
* .setMaxCount(1024)
* .setPropagateClose(false)
* .get();
* }
* </pre>
* <h2>Counting Bytes</h2>
* <p>You can set the running count when building, which is most useful when starting from another stream:
* <pre>{@code
* InputStream in = ...;
* BoundedInputStream s = BoundedInputStream.builder()
* .setInputStream(in)
* .setCount(12)
* .setMaxCount(1024)
* .setPropagateClose(false)
* .get();
* }
* </pre>
* @see Builder
* @since 2.0
*/
//@formatter:on
public class BoundedInputStream extends ProxyInputStream {
/**
* For subclassing builders from {@link BoundedInputStream} subclassses.
*
* @param <T> The subclass.
*/
static abstract class AbstractBuilder<T extends AbstractBuilder<T>> extends AbstractStreamBuilder<BoundedInputStream, T> {
/** The current count of bytes counted. */
private long count;
/** The max count of bytes to read. */
private long maxCount = EOF;
/** Flag if {@link #close()} should be propagated, {@code true} by default. */
private boolean propagateClose = true;
long getCount() {
return count;
}
long getMaxCount() {
return maxCount;
}
boolean isPropagateClose() {
return propagateClose;
}
/**
* Sets the current number of bytes counted.
* <p>
* Useful when building from another stream to carry forward a read count.
* </p>
* <p>
* Default is {@code 0}, negative means 0.
* </p>
*
* @param count The current number of bytes counted.
* @return this.
*/
public T setCount(final long count) {
this.count = Math.max(0, count);
return asThis();
}
/**
* Sets the maximum number of bytes to return.
* <p>
* Default is {@value IOUtils#EOF}, negative means unbound.
* </p>
*
* @param maxCount The maximum number of bytes to return.
* @return this.
*/
public T setMaxCount(final long maxCount) {
this.maxCount = Math.max(EOF, maxCount);
return asThis();
}
/**
* Sets whether the {@link #close()} method should propagate to the underling {@link InputStream}.
* <p>
* Default is {@code true}.
* </p>
*
* @param propagateClose {@code true} if calling {@link #close()} propagates to the {@code close()} method of the underlying stream or {@code false} if
* it does not.
* @return this.
*/
public T setPropagateClose(final boolean propagateClose) {
this.propagateClose = propagateClose;
return asThis();
}
}
//@formatter:off
/**
* Builds a new {@link BoundedInputStream}.
* <p>
* By default, a {@link BoundedInputStream} is <em>unbound</em>; so make sure to call {@link AbstractBuilder#setMaxCount(long)}.
* </p>
* <p>
* You can find out how many bytes this stream has seen so far by calling {@link BoundedInputStream#getCount()}. This value reflects bytes read and skipped.
* </p>
* <h2>Using a ServletInputStream</h2>
* <p>
* A {@code ServletInputStream} can block if you try to read content that isn't there
* because it doesn't know whether the content hasn't arrived yet or whether the content has finished. Initialize an {@link BoundedInputStream} with the
* {@code Content-Length} sent in the {@code ServletInputStream}'s header, this stop it from blocking, providing it's been sent with a correct content
* length in the first place.
* </p>
* <h2>Using NIO</h2>
* <pre>{@code
* BoundedInputStream s = BoundedInputStream.builder()
* .setPath(Paths.get("MyFile.xml"))
* .setMaxCount(1024)
* .setPropagateClose(false)
* .get();
* }
* </pre>
* <h2>Using IO</h2>
* <pre>{@code
* BoundedInputStream s = BoundedInputStream.builder()
* .setFile(new File("MyFile.xml"))
* .setMaxCount(1024)
* .setPropagateClose(false)
* .get();
* }
* </pre>
* <h2>Counting Bytes</h2>
* <p>You can set the running count when building, which is most useful when starting from another stream:
* <pre>{@code
* InputStream in = ...;
* BoundedInputStream s = BoundedInputStream.builder()
* .setInputStream(in)
* .setCount(12)
* .setMaxCount(1024)
* .setPropagateClose(false)
* .get();
* }
* </pre>
*
* @see #get()
* @since 2.16.0
*/
//@formatter:on
public static class Builder extends AbstractBuilder<Builder> {
/**
* Builds a new {@link BoundedInputStream}.
* <p>
* You must set input that supports {@link #getInputStream()}, otherwise, this method throws an exception.
* </p>
* <p>
* This builder use the following aspects:
* </p>
* <ul>
* <li>{@link #getInputStream()}</li>
* <li>maxCount</li>
* <li>propagateClose</li>
* </ul>
*
* @return a new instance.
* @throws IllegalStateException if the {@code origin} is {@code null}.
* @throws UnsupportedOperationException if the origin cannot be converted to an {@link InputStream}.
* @throws IOException if an I/O error occurs.
* @see #getInputStream()
*/
@SuppressWarnings("resource")
@Override
public BoundedInputStream get() throws IOException {
return new BoundedInputStream(getInputStream(), getCount(), getMaxCount(), isPropagateClose());
}
}
/**
* Constructs a new {@link AbstractBuilder}.
*
* @return a new {@link AbstractBuilder}.
* @since 2.16.0
*/
public static Builder builder() {
return new Builder();
}
/** The current count of bytes counted. */
private long count;
/** The max count of bytes to read. */
private final long maxCount;
/**
* Flag if close should be propagated.
*
* TODO Make final in 3.0.
*/
private boolean propagateClose = true;
/**
* Constructs a new {@link BoundedInputStream} that wraps the given input stream and is unlimited.
*
* @param in The wrapped input stream.
* @deprecated Use {@link AbstractBuilder#get()}.
*/
@Deprecated
public BoundedInputStream(final InputStream in) {
this(in, EOF);
}
/**
* Constructs a new {@link BoundedInputStream} that wraps the given input stream and limits it to a certain size.
*
* @param inputStream The wrapped input stream.
* @param maxCount The maximum number of bytes to return.
* @deprecated Use {@link AbstractBuilder#get()}.
*/
@Deprecated
public BoundedInputStream(final InputStream inputStream, final long maxCount) {
// Some badly designed methods - e.g. the Servlet API - overload length
// such that "-1" means stream finished
this(inputStream, 0, maxCount, true);
}
/**
* Constructs a new {@link BoundedInputStream} that wraps the given input stream and limits it to a certain size.
*
* @param inputStream The wrapped input stream.
* @param count The current number of bytes read.
* @param maxCount The maximum number of bytes to return.
* @param propagateClose {@code true} if calling {@link #close()} propagates to the {@code close()} method of the underlying stream or {@code false} if it
* does not.
*/
BoundedInputStream(final InputStream inputStream, final long count, final long maxCount, final boolean propagateClose) {
// Some badly designed methods - e.g. the Servlet API - overload length
// such that "-1" means stream finished
// Can't throw because we start from an InputStream.
super(inputStream);
this.count = count;
this.maxCount = maxCount;
this.propagateClose = propagateClose;
}
/**
* Adds the number of read bytes to the count.
*
* @param n number of bytes read, or -1 if no more bytes are available
* @throws IOException Not thrown here but subclasses may throw.
* @since 2.0
*/
@Override
protected synchronized void afterRead(final int n) throws IOException {
if (n != EOF) {
count += n;
}
}
/**
* {@inheritDoc}
*/
@Override
public int available() throws IOException {
if (isMaxCount()) {
onMaxLength(maxCount, getCount());
return 0;
}
return in.available();
}
/**
* Invokes the delegate's {@link InputStream#close()} method if {@link #isPropagateClose()} is {@code true}.
*
* @throws IOException if an I/O error occurs.
*/
@Override
public void close() throws IOException {
if (propagateClose) {
in.close();
}
}
/**
* Gets the count of bytes read.
*
* @return The count of bytes read.
* @since 2.12.0
*/
public long getCount() {
return count;
}
/**
* Gets the max count of bytes to read.
*
* @return The max count of bytes to read.
* @since 2.16.0
*/
public long getMaxCount() {
return maxCount;
}
/**
* Gets the max count of bytes to read.
*
* @return The max count of bytes to read.
* @since 2.12.0
* @deprecated Use {@link #getMaxCount()}.
*/
@Deprecated
public long getMaxLength() {
return maxCount;
}
/**
* Gets how many bytes remain to read.
*
* @return bytes how many bytes remain to read.
* @since 2.16.0
*/
public long getRemaining() {
return Math.max(0, getMaxCount() - getCount());
}
private boolean isMaxCount() {
return maxCount >= 0 && getCount() >= maxCount;
}
/**
* Tests whether the {@link #close()} method should propagate to the underling {@link InputStream}.
*
* @return {@code true} if calling {@link #close()} propagates to the {@code close()} method of the underlying stream or {@code false} if it does not.
*/
public boolean isPropagateClose() {
return propagateClose;
}
/**
* Invokes the delegate's {@link InputStream#mark(int)} method.
*
* @param readLimit read ahead limit
*/
@Override
public synchronized void mark(final int readLimit) {
in.mark(readLimit);
}
/**
* Invokes the delegate's {@link InputStream#markSupported()} method.
*
* @return true if mark is supported, otherwise false
*/
@Override
public boolean markSupported() {
return in.markSupported();
}
/**
* A caller has caused a request that would cross the {@code maxLength} boundary.
*
* @param maxLength The max count of bytes to read.
* @param count The count of bytes read.
* @throws IOException Subclasses may throw.
* @since 2.12.0
*/
@SuppressWarnings("unused")
protected void onMaxLength(final long maxLength, final long count) throws IOException {
// for subclasses
}
/**
* Invokes the delegate's {@link InputStream#read()} method if the current position is less than the limit.
*
* @return the byte read or -1 if the end of stream or the limit has been reached.
* @throws IOException if an I/O error occurs.
*/
@Override
public int read() throws IOException {
if (isMaxCount()) {
onMaxLength(maxCount, getCount());
return EOF;
}
return super.read();
}
/**
* Invokes the delegate's {@link InputStream#read(byte[])} method.
*
* @param b the buffer to read the bytes into
* @return the number of bytes read or -1 if the end of stream or the limit has been reached.
* @throws IOException if an I/O error occurs.
*/
@Override
public int read(final byte[] b) throws IOException {
return read(b, 0, b.length);
}
/**
* Invokes the delegate's {@link InputStream#read(byte[], int, int)} method.
*
* @param b the buffer to read the bytes into
* @param off The start offset
* @param len The number of bytes to read
* @return the number of bytes read or -1 if the end of stream or the limit has been reached.
* @throws IOException if an I/O error occurs.
*/
@Override
public int read(final byte[] b, final int off, final int len) throws IOException {
if (isMaxCount()) {
onMaxLength(maxCount, getCount());
return EOF;
}
return super.read(b, off, (int) toReadLen(len));
}
/**
* Invokes the delegate's {@link InputStream#reset()} method.
*
* @throws IOException if an I/O error occurs.
*/
@Override
public synchronized void reset() throws IOException {
in.reset();
}
/**
* Sets whether the {@link #close()} method should propagate to the underling {@link InputStream}.
*
* @param propagateClose {@code true} if calling {@link #close()} propagates to the {@code close()} method of the underlying stream or {@code false} if it
* does not.
* @deprecated Use {@link AbstractBuilder#setPropagateClose(boolean)}.
*/
@Deprecated
public void setPropagateClose(final boolean propagateClose) {
this.propagateClose = propagateClose;
}
/**
* Invokes the delegate's {@link InputStream#skip(long)} method.
*
* @param n the number of bytes to skip
* @return the actual number of bytes skipped
* @throws IOException if an I/O error occurs.
*/
@Override
public long skip(final long n) throws IOException {
final long skip = super.skip(toReadLen(n));
count += skip;
return skip;
}
private long toReadLen(final long len) {
return maxCount >= 0 ? Math.min(len, maxCount - getCount()) : len;
}
/**
* Invokes the delegate's {@link InputStream#toString()} method.
*
* @return the delegate's {@link InputStream#toString()}
*/
@Override
public String toString() {
return in.toString();
}
}