no.digipost.io.LimitedInputStream Maven / Gradle / Ivy
Show all versions of digg Show documentation
/*
* Copyright (C) Posten Norge AS
*
* Licensed 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 no.digipost.io;
import java.io.Closeable;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.function.Supplier;
import static no.digipost.DiggExceptions.asUnchecked;
/**
* An {@link InputStream} which limits how many bytes which can be read.
*
* This class is based on the
* LimitedInputStream from Apache Commons Fileupload (v1.3.2),
* which has the license as the Digg library, Apache License 2.0, but also supports
* to {@link #SILENTLY_EOF_ON_REACHING_LIMIT silently treat the limit as EOF} without any signal to distinguish between the EOF of the wrapped stream and
* the limited stream.
*/
public final class LimitedInputStream extends FilterInputStream implements Closeable {
private static final class SilentlyEofWhenReachingLimit implements Supplier {
@Override
public Exception get() {
throw new UnsupportedOperationException("Should not call get() on instance of " + SilentlyEofWhenReachingLimit.class.getSimpleName() + ", this indicates a bug.");
}
private SilentlyEofWhenReachingLimit() {}
}
/**
* Supply this instead of an {@link Supplier exception supplier} as parameter when contructing
* a new {@code LimitedInputStream} to instruct it to
* treat the limit as an ordinary EOF, and not throw any exception to signal that the
* limit was reached during consumption of the stream.
*
* Invoking {@link Supplier#get() get()} on this will throw an exception.
*/
public static final Supplier SILENTLY_EOF_ON_REACHING_LIMIT = new SilentlyEofWhenReachingLimit();
private final DataSize limit;
private final Supplier extends Exception> throwIfTooManyBytes;
private long count;
/**
* @see no.digipost.DiggIO#limit(InputStream, DataSize, Supplier)
*/
public LimitedInputStream(InputStream inputStream, DataSize maxDataToRead, Supplier extends Exception> throwIfTooManyBytes) {
super(inputStream);
this.limit = maxDataToRead;
this.throwIfTooManyBytes = throwIfTooManyBytes;
}
/**
* Reads the next byte of data from this input stream. The value
* byte is returned as an int
in the range
* 0
to 255
. If no byte is available
* because the end of the stream has been reached, the value
* -1
is returned. This method blocks until input data
* is available, the end of the stream is detected, or an exception
* is thrown.
*
* This method
* simply performs in.read()
and returns the result.
*
* @return the next byte of data, or -1
if the end of the
* stream is reached.
* @exception IOException if an I/O error occurs.
* @see java.io.FilterInputStream#in
*/
@Override
public int read() throws IOException {
int res = super.read();
if (res != -1) {
count++;
if (hasReachedLimit()) {
return -1;
}
}
return res;
}
/**
* Reads up to len
bytes of data from this input stream
* into an array of bytes. If len
is not zero, the method
* blocks until some input is available; otherwise, no
* bytes are read and 0
is returned.
*
* This method simply performs in.read(b, off, len)
* and returns the result.
*
* @param b the buffer into which the data is read.
* @param off The start offset in the destination array
* b
.
* @param len the maximum number of bytes read.
* @return the total number of bytes read into the buffer, or
* -1
if there is no more data because the end of
* the stream has been reached.
* @exception NullPointerException If b
is null
.
* @exception IndexOutOfBoundsException If off
is negative,
* len
is negative, or len
is greater than
* b.length - off
* @exception IOException if an I/O error occurs.
* @see java.io.FilterInputStream#in
*/
@Override
public int read(byte[] b, int off, int len) throws IOException {
int res = super.read(b, off, len);
if (res > 0) {
count += res;
if (hasReachedLimit()) {
return -1;
}
}
return res;
}
private boolean hasReachedLimit() throws IOException {
if (count > limit.toBytes()) {
if (throwIfTooManyBytes == SILENTLY_EOF_ON_REACHING_LIMIT) {
return true;
}
Exception tooManyBytes = throwIfTooManyBytes.get();
if (tooManyBytes instanceof IOException) {
throw (IOException) tooManyBytes;
} else {
throw asUnchecked(tooManyBytes);
}
} else {
return false;
}
}
}