File Input Output Java

/*
 * 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  * "http://ostermiller.org/utils/">ostermiller.org.
 *
 * @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 inputStreamQueue = new ArrayList();
  /**
   * 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.
   * 


   * 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.
   * 


   * 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).
   * 


   * 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.
   * 


   * 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.
   * 


   * 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.
   * 


   * The read(b) method for class InputStream has the same effect as:

   * read(b, 0, b.length)
   * 


   * 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.
   * 


   * If length is zero,
   * then no bytes are read and 0 is returned; otherwise, there is an attempt
   * to read at least one byte.
   * 


   * This method blocks until input data is available
   * 


   * 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.
   * 


   * 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;
  }
}