summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Fankhauser hiddenalpha.ch2022-12-29 23:07:01 +0100
committerAndreas Fankhauser hiddenalpha.ch2022-12-29 23:07:01 +0100
commitb89ec523c123a31a41a2f35ff58b84e6b17161d0 (patch)
tree0f219fc28d9645ec36b0bab777bc3bdd0a774ed1
parente847cbf43574b3ee34f06b4de397baa05b54287e (diff)
downloadxtra4j-b89ec523c123a31a41a2f35ff58b84e6b17161d0.zip
xtra4j-b89ec523c123a31a41a2f35ff58b84e6b17161d0.tar.gz
Add ConcatInputStream
-rw-r--r--xtra4j-misc/src/main/java/ch/hiddenalpha/xtra4j/octetstream/ConcatInputStream.java97
1 files changed, 97 insertions, 0 deletions
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;
+ }
+
+}