An input stream which reads sequentially from multiple sources : InputStream « File Input Output « Java






An input stream which reads sequentially from multiple sources

    
/*
 * Copyright (C) 2004 Stephen Ostermiller
 * http://ostermiller.org/contact.pl?regarding=Java+Utilities
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * See COPYING.TXT for details.
 */

import java.io.*;
import java.util.ArrayList;

/**
 * An input stream which reads sequentially from multiple sources.
 * More information about this class is available from <a target="_top" href=
 * "http://ostermiller.org/utils/">ostermiller.org</a>.
 *
 * @author Stephen Ostermiller http://ostermiller.org/contact.pl?regarding=Java+Utilities
 * @since ostermillerutils 1.04.00
 */
public class ConcatInputStream extends InputStream {


  /**
   * Current index to inputStreamQueue
   *
   * @since ostermillerutils 1.04.01
   */
  private int inputStreamQueueIndex = 0;

  /**
   * Queue of inputStreams that have yet to be read from.
   *
   * @since ostermillerutils 1.04.01
   */
  private ArrayList<InputStream> inputStreamQueue = new ArrayList<InputStream>();

  /**
   * A cache of the current inputStream from the inputStreamQueue
   * to avoid unneeded access to the queue which must
   * be synchronized.
   *
   * @since ostermillerutils 1.04.01
   */
  private InputStream currentInputStream = null;

  /**
   * true iff the client may add more inputStreams.
   *
   * @since ostermillerutils 1.04.01
   */
  private boolean doneAddingInputStreams = false;

  /**
   * Causes the addInputStream method to throw IllegalStateException
   * and read() methods to return -1 (end of stream)
   * when there is no more available data.
   * <p>
   * Calling this method when this class is no longer accepting
   * more inputStreams has no effect.
   *
   * @since ostermillerutils 1.04.01
   */
  public void lastInputStreamAdded(){
    doneAddingInputStreams = true;
  }

  /**
   * Add the given inputStream to the queue of inputStreams from which to
   * concatenate data.
   *
   * @param in InputStream to add to the concatenation.
   * @throws IllegalStateException if more inputStreams can't be added because lastInputStreamAdded() has been called, close() has been called, or a constructor with inputStream parameters was used.
   *
   * @since ostermillerutils 1.04.01
   */
  public void addInputStream(InputStream in){
    synchronized(inputStreamQueue){
      if (in == null) throw new NullPointerException();
      if (closed) throw new IllegalStateException("ConcatInputStream has been closed");
      if (doneAddingInputStreams) throw new IllegalStateException("Cannot add more inputStreams - the last inputStream has already been added.");
      inputStreamQueue.add(in);
    }
  }

  /**
   * Add the given inputStream to the queue of inputStreams from which to
   * concatenate data.
   *
   * @param in InputStream to add to the concatenation.
   * @throws IllegalStateException if more inputStreams can't be added because lastInputStreamAdded() has been called, close() has been called, or a constructor with inputStream parameters was used.
   * @throws NullPointerException the array of inputStreams, or any of the contents is null.
   *
   * @since ostermillerutils 1.04.01
   */
  public void addInputStreams(InputStream[] in){
    for (InputStream element: in) {
      addInputStream(element);
    }
  }

  /**
   * Gets the current inputStream, looking at the next
   * one in the list if the current one is null.
   *
   * @since ostermillerutils 1.04.01
   */
  private InputStream getCurrentInputStream(){
    if (currentInputStream == null && inputStreamQueueIndex < inputStreamQueue.size()){
      synchronized(inputStreamQueue){
        // inputStream queue index is advanced only by the nextInputStream()
        // method.  Don't do it here.
        currentInputStream = inputStreamQueue.get(inputStreamQueueIndex);
      }
    }
    return currentInputStream;
  }

  /**
   * Indicate that we are done with the current inputStream and we should
   * advance to the next inputStream.
   *
   * @since ostermillerutils 1.04.01
   */
  private void advanceToNextInputStream(){
    currentInputStream = null;
    inputStreamQueueIndex++;
  }

  /**
   * True iff this the close() method has been called on this stream.
   *
   * @since ostermillerutils 1.04.00
   */
  private boolean closed = false;


  /**
   * Create a new input stream that can dynamically accept new sources.
   * <p>
   * New sources should be added using the addInputStream() method.
   * When all sources have been added the lastInputStreamAdded() should
   * be called so that read methods can return -1 (end of stream).
   * <p>
   * Adding new sources may by interleaved with read calls.
   *
   * @since ostermillerutils 1.04.01
   */
  public ConcatInputStream(){
    // Empty constructor
  }

  /**
   * Create a new InputStream with one source.
   *
   * @param in InputStream to use as a source.
   *
   * @throws NullPointerException if in is null
   *
   * @since ostermillerutils 1.04.00
   */
  public ConcatInputStream(InputStream in){
    addInputStream(in);
    lastInputStreamAdded();
  }

  /**
   * Create a new InputStream with two sources.
   *
   * @param in1 first InputStream to use as a source.
   * @param in2 second InputStream to use as a source.
   *
   * @throws NullPointerException if either source is null.
   *
   * @since ostermillerutils 1.04.00
   */
  public ConcatInputStream(InputStream in1, InputStream in2){
    addInputStream(in1);
    addInputStream(in2);
    lastInputStreamAdded();
  }

  /**
   * Create a new InputStream with an arbitrary number of sources.
   *
   * @param in InputStreams to use as a sources.
   *
   * @throws NullPointerException if the input array on any element is null.
   *
   * @since ostermillerutils 1.04.00
   */
  public ConcatInputStream(InputStream[] in){
    addInputStreams(in);
    lastInputStreamAdded();
  }

  /**
   * Reads the next byte of data from the underlying streams. 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.
   * <p>
   * If this class in not done accepting inputstreams and the end of the last known
   * stream is reached, this method will block forever unless another thread
   * adds an inputstream or interrupts.
   *
   * @return the next byte of data, or -1 if the end of the stream is reached.
   *
   * @throws IOException if an I/O error occurs.
   */
  @Override public int read() throws IOException {
    if (closed) throw new IOException("InputStream closed");
    int r = -1;
    while (r == -1){
      InputStream in = getCurrentInputStream();
      if (in == null){
        if (doneAddingInputStreams) return -1;
        try {
          Thread.sleep(100);
        } catch (InterruptedException iox){
          throw new IOException("Interrupted");
        }
      } else {
        r = in.read();
        if (r == -1) advanceToNextInputStream();
      }
    }
    return r;
  }

  /**
   * Reads some number of bytes from the underlying streams and stores them into
   * the buffer array b. The number of bytes actually read is returned as an
   * integer. This method blocks until input data is available, end of file is
   * detected, or an exception is thrown.
   * <p>
   * If the length of b is zero,
   * then no bytes are read and 0 is returned; otherwise, there is an attempt
   * to read at least one byte.
   * <p>
   * The read(b) method for class InputStream has the same effect as:<br>
   * read(b, 0, b.length)
   * <p>
   * If this class in not done accepting inputstreams and the end of the last known
   * stream is reached, this method will block forever unless another thread
   * adds an inputstream or interrupts.
   *
   * @param b - Destination buffer
   * @return The number of bytes read, or -1 if the end of the stream has been reached
   *
   * @throws IOException - If an I/O error occurs
   * @throws NullPointerException - If b is null.
   *
   * @since ostermillerutils 1.04.00
   */
  @Override public int read(byte[] b) throws IOException {
    return read(b, 0, b.length);
  }

  /**
   * Reads up to length bytes of data from the underlying streams into an array of bytes.
   * An attempt is made to read as many as length bytes, but a smaller number may be read,
   * possibly zero. The number of bytes actually read is returned as an integer.
   * <p>
   * If length is zero,
   * then no bytes are read and 0 is returned; otherwise, there is an attempt
   * to read at least one byte.
   * <p>
   * This method blocks until input data is available
   * <p>
   * If this class in not done accepting inputstreams and the end of the last known
   * stream is reached, this method will block forever unless another thread
   * adds an inputstream or interrupts.
   *
   * @param b Destination buffer
   * @param off Offset at which to start storing bytes
   * @param len Maximum number of bytes to read
   * @return The number of bytes read, or -1 if the end of the stream has been reached
   *
   * @throws IOException - If an I/O error occurs
   * @throws NullPointerException - If b is null.
   * @throws IndexOutOfBoundsException - if length or offset are not possible.
   */
  @Override public int read(byte[] b, int off, int len) throws IOException {
    if (off < 0 || len < 0 || off + len > b.length) throw new IllegalArgumentException();
    if (closed) throw new IOException("InputStream closed");
    int r = -1;
    while (r == -1){
      InputStream in = getCurrentInputStream();
      if (in == null){
        if (doneAddingInputStreams) return -1;
        try {
          Thread.sleep(100);
        } catch (InterruptedException iox){
          throw new IOException("Interrupted");
        }
      } else {
        r = in.read(b, off, len);
        if (r == -1) advanceToNextInputStream();
      }
    }
    return r;
  }

  /**
   * Skips over and discards n bytes of data from this input stream. The skip method
   * may, for a variety of reasons, end up skipping over some smaller number of bytes,
   * possibly 0. This may result from any of a number of conditions; reaching end of
   * file before n bytes have been skipped is only one possibility. The actual number
   * of bytes skipped is returned. If n is negative, no bytes are skipped.
   * <p>
   * If this class in not done accepting inputstreams and the end of the last known
   * stream is reached, this method will block forever unless another thread
   * adds an inputstream or interrupts.
   *
   * @param n he number of characters to skip
   * @return The number of characters actually skipped
   *
   * @throws IOException If an I/O error occurs
   *
   * @since ostermillerutils 1.04.00
   */
  @Override public long skip(long n) throws IOException {
    if (closed) throw new IOException("InputStream closed");
    if (n <= 0) return 0;
    long s = -1;
    while (s <= 0){
      InputStream in = getCurrentInputStream();
      if (in == null){
        if (doneAddingInputStreams) return 0;
        try {
          Thread.sleep(100);
        } catch (InterruptedException iox){
          throw new IOException("Interrupted");
        }
      } else {
        s = in.skip(n);
        // When nothing was skipped it is a bit of a puzzle.
        // The most common cause is that the end of the underlying
        // stream was reached.  In which case calling skip on it
        // will always return zero.  If somebody were calling skip
        // until it skipped everything they needed, there would
        // be an infinite loop if we were to return zero here.
        // If we get zero, let us try to read one character so
        // we can see if we are at the end of the stream.  If so,
        // we will move to the next.
        if (s <= 0) {
          // read() will advance to the next stream for us, so don't do it again
          s = ((read()==-1)?-1:1);
        }
      }

    }
    return s;
  }

  /**
   * Returns the number of bytes that can be read (or skipped over) from this input
   * stream without blocking by the next caller of a method for this input stream.
   * The next caller might be the same thread or or another thread.
   *
   * @throws IOException If an I/O error occurs
   *
   * @since ostermillerutils 1.04.00
   */
  @Override public int available() throws IOException {
    if (closed) throw new IOException("InputStream closed");
    InputStream in = getCurrentInputStream();
    if (in == null) return 0;
    return in.available();
  }

  /**
   * Closes this input stream and releases any system resources associated with the stream.
   *
   * @since ostermillerutils 1.04.00
   */
  @Override public void close() throws IOException {
    if (closed) return;
    for (Object element: inputStreamQueue) {
      ((InputStream)element).close();
    }
    closed = true;
  }

  /**
   * Mark not supported
   *
   * @since ostermillerutils 1.04.00
   */
  @Override public void mark(int readlimit){
    // Mark not supported -- do nothing
  }

  /**
   * Reset not supported.
   *
   * @throws IOException because reset is not supported.
   *
   * @since ostermillerutils 1.04.00
   */
  @Override public void reset() throws IOException {
    throw new IOException("Reset not supported");
  }

  /**
   * Does not support mark.
   *
   * @return false
   *
   * @since ostermillerutils 1.04.00
   */
  @Override public boolean markSupported(){
    return false;
  }
}

   
    
    
    
  








Related examples in the same category

1.Creating an input or output stream on a ByteBuffer
2.Creating a Manifest for a JAR File
3.Convert InputStream to String
4.An limited-data-size input stream
5.Combined InputStream
6.Size Limit InputStream
7.Minimal InputStream subclass to fetch bytes form a String
8.Read and return the entire contents of the supplied InputStream. This method always closes the stream when finished reading.
9.Compare two InputStream
10.Read and return the entire contents of the supplied InputStream.
11.Deserializes an object from an input stream.
12.Reads at most certain bytes from input stream and returns them as a byte array.
13.Convert Reader to InputStream
14.A trace of the data that is being retrieved from an input stream
15.EOLConvertingInputStream: InputStream which converts \r bytes not followed by \n and \n not preceded by \r to \r\n.
16.A line reading wrapper that works with byte streams.
17.Smart Encoding InputStream
18.Wraps a stream that has been exported to import the data in readable form
19.This input stream wrapper closes the base input stream when fully read.
20.LZFInputStream and LZFOutputStream
21.Random input stream