/* * This file is part of muCommander, http://www.mucommander.com * Copyright (C) 2002-2016 Maxence Bernard * * muCommander is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * muCommander is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.mucommander.commons.io; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; /** * <code>BoundedInputStream</code> is an InputStream that has a set limit to the number of bytes that can be read or * skipped from it. What happens when the limit is reached is controlled at creation time: <code>read</code> and * <code>skip</code> methods can either throw a {@link StreamOutOfBoundException} or simply return <code>-1</code>. * * <p>The limit has no effect if it is set to a value that is higher than the number of bytes remaining in the * underlying stream.</p> * * <p>This class is particularly useful for reading archives that are a concatenation of files, tarballs for instance.</p> * * @author Maxence Bernard * @see BoundedReader * @see BoundedOutputStream * @see StreamOutOfBoundException */ public class BoundedInputStream extends FilterInputStream implements Bounded { private long totalRead; private long allowedBytes; private boolean throwStreamOutOfBoundException; /** * Creates a new <code>BoundedInputStream</code> over the specified stream, allowing a maximum of * <code>allowedBytes</code> to be read or skipped. If <code>allowedBytes</code> is equal to <code>-1</code>, this * stream is not bounded and acts as a normal stream. * * <p>If the <code>throwStreamOutOfBoundException</code> parameter is <code>true</code>, <code>read</code> and * <code>skip</code> methods will throw a {@link StreamOutOfBoundException} when an attempt to read or skip beyond * that limit is made. If <code>false</code>, <code>-1</code> will be returned.</p> * * @param in the stream to be bounded * @param allowedBytes the total number of bytes that are allowed to be read or skipped, <code>-1</code> for no limit * @param throwStreamOutOfBoundException <code>true</code> to throw when an attempt to read or skip beyond the byte * limit is made, <code>false</code> to simply return <code>-1</code> */ public BoundedInputStream(InputStream in, long allowedBytes, boolean throwStreamOutOfBoundException) { super(in); this.allowedBytes = allowedBytes; this.throwStreamOutOfBoundException = throwStreamOutOfBoundException; } /** * Called when an attempt to read out of the stream's bound has been made. This method will either throw a * {@link StreamOutOfBoundException} or return <code>-1</code>, depending on how the <code>BoundedInputStream</code> * was created. * * @return -1 if this BoundedInputStream was configured not to throw a StreamOutOfBoundException * @throws StreamOutOfBoundException if this BoundedInputStream was configured to throw a StreamOutOfBoundException */ protected int handleStreamOutOfBound() throws StreamOutOfBoundException { if(throwStreamOutOfBoundException) throw new StreamOutOfBoundException(allowedBytes); return -1; } //////////////////////////// // Bounded implementation // //////////////////////////// public long getAllowedBytes() { return allowedBytes; } public synchronized long getProcessedBytes() { return totalRead; } public synchronized long getRemainingBytes() { return allowedBytes<=-1?Long.MAX_VALUE:allowedBytes-totalRead; } //////////////////////// // Overridden methods // //////////////////////// @Override public synchronized int read() throws IOException { if(getRemainingBytes()==0) return handleStreamOutOfBound(); int i = in.read(); totalRead++; return i; } @Override public int read(byte b[]) throws IOException { return read(b, 0, b.length); } @Override public synchronized int read(byte b[], int off, int len) throws IOException { int canRead = (int)Math.min(getRemainingBytes(), len); if(canRead==0) return handleStreamOutOfBound(); int nbRead = in.read(b, off, canRead); if(nbRead>0) totalRead += nbRead; return nbRead; } @Override public synchronized long skip(long n) throws IOException { int canSkip = (int)Math.min(getRemainingBytes(), n); if(canSkip==0) return handleStreamOutOfBound(); long nbSkipped = in.skip(canSkip); if(nbSkipped>0) totalRead += nbSkipped; return nbSkipped; } @Override public synchronized int available() throws IOException { return Math.min(in.available(), (int)getRemainingBytes()); } // Methods not implemented /** * Always returns <code>false</code>, even if the underlying stream supports it. * * @return always returns <code>false</code>, even if the underlying stream supports it */ @Override public boolean markSupported() { // Todo: in theory we could support mark/reset return false; } /** * Implemented as a no-op: the call is *not* delegated to the underlying stream. */ @Override public synchronized void mark(int readlimit) { // Todo: in theory we could support mark/reset // No-op } /** * Always throws an <code>IOException</code>: the call is *not* delegated to the underlying stream. */ @Override public synchronized void reset() throws IOException { // Todo: in theory we could support mark/reset // No-op } }