/*
* 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.compound;
import java.io.IOException;
import java.io.InputStream;
/**
* <code>CompoundInputStream</code> concatenates several input streams into one. It can operate in two modes:
* <dl>
* <dt>Merged</dt>
* <dd>the compound stream acts as a single, global input stream, merging the contents of underlying streams. The
* compound stream can be read just like a regular <code>InputStream</code> -- streams are advanced automatically as EOF
* of individual streams are reached.</dd>
*
* <dt>Unmerged</dt>
* <dd>the compound stream has be advanced manually. EOF are signaled individually for each underlying stream.
* After EOF is reached, the current stream has to be advanced to the next one using {@link #advanceInputStream()}.</dd>
* </dl>
*
* <p>
* This class is abstract, with a single method to implement: {@link #getNextInputStream()}.
* See {@link IteratorCompoundInputStream} for an <code>Iterator</code>-backed implementation.
* </p>
*
* @see IteratorCompoundInputStream
* @see CompoundReader
* @author Maxence Bernard
*/
public abstract class CompoundInputStream extends InputStream {
/** True if this CompoundInputStream operates in 'merged' mode */
private boolean merged;
/** The InputStream that's currently being processed */
private InputStream currentIn;
/** Used by {@link #read()} */
private byte oneByteBuf[];
/** <code>true</code> if the global EOF has been reached */
private boolean globalEOFReached;
/**
* Creates a new <code>CompoundInputStream</code> operating in the specified mode.
*
* @param merged <code>true</code> if the streams should be merged, acting as a single stream, or considered
* as separate streams that have to be {@link #advanceInputStream() advanced manually}.
*/
public CompoundInputStream(boolean merged) {
this.merged = merged;
}
/**
* Returns:
* <ul>
* <li><code>true</code> if this stream acts as a single, global input stream, merging the contents of underlying
* streams. In this mode, the compound stream can be read just like a regular InputStream -- streams are advanced
* automatically as EOF of individual streams are reached.</li>
* <li><code>false</code> if this stream has be advanced manually. In this mode, EOF are signaled individually
* for each underlying stream. After EOF has been reached, the current stream has to be advanced to the next one
* using {@link #advanceInputStream()}.</li>
* </ul>
*
* @return <code>true</code> if this stream acts as a global input stream, <code>false</code> if this stream has
* to be advanced manually.
*/
public boolean isMerged() {
return merged;
}
/**
* Returns the <code>InputStream</code> this compound stream is currently reading, <code>null</code> if this stream
* hasn't read anything yet, or if it has no underlying stream to read.
*
* @return <code>InputStream</code> this compound stream is currently reading
*/
public InputStream getCurrentInputStream() {
return currentIn;
}
/**
* Closes the current input stream, if any. This method has no effect if there is no current input stream.
*
* @throws IOException if an error occurred while closing the current input stream.
*/
public void closeCurrentInputStream() throws IOException {
if(currentIn!=null)
currentIn.close();
}
/**
* Tries to advances the current stream to the next one, causing subsequent calls to <code>InputStream</code>
* methods to operate on the new stream. Returns <code>true</code> if there was a next stream, <code>false</code>
* otherwise.
* <p>
* Note: the current stream (if any) will be closed by this method.
* </p>
*
* @return <code>true</code> if there was a next stream, <code>false</code> otherwise
* @throws IOException if an error occurred while trying to advancing the current stream. This
* <code>CompoundInputStream</code> can't be used after that and must be closed.
*/
public boolean advanceInputStream() throws IOException {
// Return immediately (don't close the stream) if this method is global EOF has already been reached
if(globalEOFReached)
return false;
// Close the current stream
if(currentIn!=null) {
try {
closeCurrentInputStream();
}
catch(IOException e) {
// Fail silently
}
}
// Try to advance the current InputStream to the next
try {
currentIn = getNextInputStream();
}
catch(IOException e) {
// Can't recover from this, this is the end of this stream
globalEOFReached = true;
throw e;
}
if(currentIn==null) {
// Global EOF reached
globalEOFReached = true;
return false;
}
return true;
}
/**
* Checks the current stream and returns <code>true</code> if the current stream is in a state where it can be
* accessed, <code>false</code> if global EOF has been reached.
*
* @return <code>true</code> if the current stream is in a state where it can be accessed, <code>false</code> if
* global EOF has been reached.
* @throws IOException if an error occurred while trying to advancing the current stream.
*/
private boolean checkStream() throws IOException {
if(globalEOFReached)
return true;
if(currentIn==null)
if(!advanceInputStream())
return true;
return false;
}
//////////////////////
// Abstract methods //
//////////////////////
/**
* Returns the next <code>InputStream</code>, <code>null</code> if there is none.
* <p>
* Before calling this method, {@link #advanceInputStream()} closes the current stream (if any). In other words,
* implementations do not have to worry about closing previously-returned streams.
* </p>
*
* @return the next <code>InputStream</code>, <code>null</code> if there is none.
* @throws IOException if an error occurred while retrieving the next input stream
*/
public abstract InputStream getNextInputStream() throws IOException;
////////////////////////////////
// InputStream implementation //
////////////////////////////////
/**
* Delegates to {@link #read(byte[], int, int)} with a 1-byte buffer.
*/
@Override
public int read() throws IOException {
if(oneByteBuf==null)
oneByteBuf = new byte[1];
int ret = read(oneByteBuf, 0, 1);
return ret<=0?ret:oneByteBuf[0];
}
/**
* Delegates to {@link #read(byte[], int, int)} with a <code>0</code> offset and the whole buffer's length.
*/
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
/**
* Reads up to <code>len-off</code> bytes and stores them in the specified byte buffer, starting at <code>off</code>.
* Returns the number of bytes that were actually read, or <code>-1</code> to signal:
* <ul>
* <li>if {@link #isMerged()} is <code>true</code>, the end of the compound stream as a whole</li>
* <li>if {@link #isMerged ()} is <code>false</code>, the end of the current stream, which may or may not coincide
* with the end of the stream as a whole.</li>
* </ul>
*/
@Override
public int read(byte[] b, int off, int len) throws IOException {
if(checkStream())
return -1;
int ret = currentIn.read(b, off, len);
if(ret==-1) {
// read the next stream
if(merged) {
if(!advanceInputStream())
return -1; // Global EOF reached
// Recurse
return read(b, off, len);
}
return -1;
}
return ret;
}
/**
* Skips up to <code>n</code> bytes and returns the number of bytes that were actually skipped, or <code>-1</code>
* to signal:
* <ul>
* <li>if {@link #isMerged()} is enabled, the end of the compound stream as a whole</li>
* <li>if {@link #isMerged ()} is disabled, the end of the current stream, which may or may not coincide
* with the end of the stream as a whole.</li>
* </ul>
*/
@Override
public long skip(long n) throws IOException {
if(checkStream())
return -1;
long ret = currentIn.skip(n);
if(ret==-1) {
// read the next stream
if(merged) {
if(!advanceInputStream())
return -1; // Global EOF reac hed
return currentIn.skip(n);
}
return -1;
}
return ret;
}
/**
* Closes the current <code>InputStream</code> and this <code>CompoundInputStream</code> a whole.
* The current stream can no longer be advanced after this method has been called.
*
* @throws IOException if the current stream could not be closed.
*/
@Override
public void close() throws IOException {
try {
if(currentIn!=null)
closeCurrentInputStream();
}
finally {
globalEOFReached = true;
}
}
/**
* Delegates to the current <code>InputStream</code>.
*/
@Override
public int available() throws IOException {
if(checkStream())
return 0;
return currentIn.available();
}
/**
* Delegates to the current <code>InputStream</code>.
*/
@Override
public void mark(int readlimit) {
try {
if(!checkStream())
currentIn.mark(readlimit);
}
catch(IOException e) {
// Can't throw an IOException here unfortunately, fail silently
}
}
/**
* Delegates to the current <code>InputStream</code>.
*/
@Override
public void reset() throws IOException {
if(!checkStream())
currentIn.reset();
}
/**
* Delegates to the current <code>InputStream</code>.
*/
@Override
public boolean markSupported() {
try {
return !checkStream() && currentIn.markSupported();
}
catch(IOException e) {
return false;
}
}
}