From b89ec523c123a31a41a2f35ff58b84e6b17161d0 Mon Sep 17 00:00:00 2001 From: Andreas Fankhauser hiddenalpha.ch Date: Thu, 29 Dec 2022 23:07:01 +0100 Subject: Add ConcatInputStream --- .../xtra4j/octetstream/ConcatInputStream.java | 97 ++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 xtra4j-misc/src/main/java/ch/hiddenalpha/xtra4j/octetstream/ConcatInputStream.java diff --git a/xtra4j-misc/src/main/java/ch/hiddenalpha/xtra4j/octetstream/ConcatInputStream.java b/xtra4j-misc/src/main/java/ch/hiddenalpha/xtra4j/octetstream/ConcatInputStream.java new file mode 100644 index 0000000..7ed8754 --- /dev/null +++ b/xtra4j-misc/src/main/java/ch/hiddenalpha/xtra4j/octetstream/ConcatInputStream.java @@ -0,0 +1,97 @@ +package ch.hiddenalpha.xtra4j.octetstream; + +import java.io.IOException; +import java.io.InputStream; + + +/** + * Concatenates multiple {@link InputStream}s into one stream. + */ +public class ConcatInputStream extends InputStream { + + private InputStream[] sources; + private int iSrc; + + public ConcatInputStream( InputStream... sources ){ + assert sources != null; + this.sources = sources; + this.iSrc = 0; + } + + @Override + public int read( byte[] b, int off, int len ) throws IOException { + int copied = 0; + while( true ){ + if( copied == len ){ // Request fully served + return len; } + if( isEof() ){ + return (copied == 0) ? -1 : copied; } + InputStream src = sources[iSrc]; + int readLen = src.read(b, off + copied, len - copied); + if( readLen < 0 ){ + assert readLen == -1 : "Streams MUST NOT return negative values other than -1"; + // Source drained. Continue read with next source. + shiftToNextSource(); + continue; + } + copied += readLen; + } + } + + @Override + public int read() throws IOException { + while( !isEof() ){ + InputStream src = sources[iSrc]; + int read = src.read(); + assert read >= -1 : "Streams MUST NOT return negative values other than -1"; + if( read == -1 ){ + shiftToNextSource(); + continue; // Try reading from next source. + } + return read; + } + return -1; // No more sources. We're done. + } + + @Override + public void close() throws IOException { + // Close all remaining sources. + Exception firstException = null; + for( int i = iSrc ; i < sources.length ; ++i ){ + try{ + sources[i].close(); + }catch( IOException|RuntimeException ex ){ + if( firstException == null ){ + // Track the exception. But we have to close the + // remaining streams regardless of early exceptions. + firstException = ex; + }else if( firstException != ex ){ + firstException.addSuppressed(ex); + } + } + } + sources = null; // GC is not a magician. So tell him what we know. + // Bubble exception if we had any. + if( firstException instanceof RuntimeException ){ + throw (RuntimeException)firstException; + }else if( firstException != null ){ + throw (IOException)firstException; + } + } + + private void shiftToNextSource() throws IOException { + if( !isEof() ){ + InputStream oldSrc = sources[iSrc]; + sources[iSrc] = null; + iSrc += 1; + // Calling close as last step to prevent trouble with our + // state as callee potentially could throw. + oldSrc.close(); + } + } + + private boolean isEof() { + return iSrc >= sources.length; + } + +} -- cgit v1.1