StreamUtils.java

/*
 * StreamUtils
 *
 * $Id$
 * $HeadURL$
 */
package gov.usgs.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilterInputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.zip.GZIPInputStream;

/**
 * Stream input, output, and transfer utilities.
 */
public class StreamUtils {

  /** Default buffer size used for stream reads and writes. */
  public static final int DEFAULT_BUFFER_SIZE = 4096;

  /** Default connect timeout for url connections. */
  public static final int DEFAULT_URL_CONNECT_TIMEOUT = 5000;

  /** Default read timeout for url connections. */
  public static final int DEFAULT_URL_READ_TIMEOUT = 15000;

  /**
   * Get an input stream for an Object if possible.
   *
   * @param obj an InputStream, File, byte[], or String.
   * @return an InputStream or null. If obj is a File, the stream is Buffered.
   * @throws IOException              if an error occurs.
   * @throws IllegalArgumentException if obj is not an InputStream, URL, File,
   *                                  byte[], or String.
   */
  public static InputStream getInputStream(final Object obj) throws IOException, IllegalArgumentException {
    InputStream stream = null;
    byte[] bytes = null;

    if (obj instanceof InputStream) {
      stream = (InputStream) obj;
    } else if (obj instanceof URL) {
      stream = getURLInputStream((URL) obj);
    } else if (obj instanceof File) {
      stream = new BufferedInputStream(new FileInputStream((File) obj));
    } else if (obj instanceof byte[]) {
      bytes = (byte[]) obj;
    } else if (obj instanceof String) {
      bytes = ((String) obj).getBytes();
    } else {
      throw new IllegalArgumentException("Expected an InputStream, URL, File, byte[], or String");
    }

    if (bytes != null) {
      stream = new ByteArrayInputStream(bytes);
    }

    return stream;
  }

  /**
   * Get an InputStream from a URL. If URL is a HTTP url, attempts gzip
   * compression.
   *
   * @param url the url being accessed.
   * @return an InputStream to content at URL.
   * @throws IOException if an error occurs.
   */
  public static InputStream getURLInputStream(final URL url) throws IOException {
    return getURLInputStream(url, DEFAULT_URL_CONNECT_TIMEOUT, DEFAULT_URL_READ_TIMEOUT);
  }

  /**
   * Get an InputStream from a URL. If URL is a HTTP url, attempts gzip
   * compression.
   *
   * @param url            the url being accessed.
   * @param connectTimeout allowed time in milliseconds before connection.
   * @param readTimeout    allowed time in milliseconds before read.
   * @return an InputStream to content at URL.
   * @throws IOException if an error occurs.
   */
  public static InputStream getURLInputStream(final URL url, final int connectTimeout, final int readTimeout)
      throws IOException {
    InputStream in = null;

    // initialize connection
    URLConnection conn = url.openConnection();
    conn.setRequestProperty("Accept-Encoding", "gzip");
    conn.setConnectTimeout(connectTimeout);
    conn.setReadTimeout(readTimeout);

    // connect
    conn.connect();

    // get response
    in = conn.getInputStream();
    String contentEncoding = conn.getContentEncoding();
    if (contentEncoding != null && contentEncoding.equals("gzip")) {
      in = new GZIPInputStream(in);
    }

    return in;
  }

  /**
   * Turn an object into an OutputStream if possible.
   *
   * @param obj    an OutputStream or File.
   * @param append if obj is a file and this parameter is true, the output stream
   *               will be opened in append mode.
   * @return an OutputStream. If obj is a File, the stream is Buffered.
   * @throws IOException if an error occurs
   */
  public static OutputStream getOutputStream(final Object obj, final boolean append) throws IOException {
    OutputStream stream = null;

    if (obj instanceof OutputStream) {
      stream = (OutputStream) obj;
    } else if (obj instanceof File) {
      File file = (File) obj;
      // create parent directory first, if needed
      File parent = file.getAbsoluteFile().getParentFile();
      if (!parent.exists() && !parent.mkdirs()) {
        throw new IOException("Unable to create directory " + parent.getAbsolutePath());
      }
      if (!parent.canWrite()) {
        throw new IOException("Do not have write permission for directory " + parent.getAbsolutePath());
      }

      stream = new BufferedOutputStream(new FileOutputStream(file, append));
    } else {
      throw new IllegalArgumentException("Expected an OutputStream or File");
    }

    return stream;
  }

  /**
   * Same as calling getOutputStream(obj, false). If obj is a file, it will open a
   * new output stream and will not append.
   *
   * @param obj an OutputStream or File.
   * @return an OutputStream. If obj is a File, the stream is Buffered.
   * @throws IOException if an error occurs.
   */
  public static OutputStream getOutputStream(final Object obj) throws IOException {
    return getOutputStream(obj, false);
  }

  /**
   * Read stream contents into a byte array.
   *
   * @param from stream to read.
   * @return byte array of file content.
   * @throws IOException if an error occurs while reading.
   */
  public static byte[] readStream(final Object from) throws IOException {
    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    transferStream(from, buffer);
    return buffer.toByteArray();
  }

  /**
   * Transfer contents of Object from to Object to.
   *
   * Calls transferStream(from, to, DEFAULT_BUFFER_SIZE).
   *
   * @param from streamable source.
   * @param to   streamable target.
   * @throws IOException if IO error occurs
   */
  public static void transferStream(final Object from, final Object to) throws IOException {
    transferStream(from, to, DEFAULT_BUFFER_SIZE);
  }

  /**
   * Transfer contents of Object from to Object to. Uses getInputStream and
   * getOutputStream to generate streams. Streams are closed after reading.
   *
   * @param from       streamable source.
   * @param to         streamable target.
   * @param bufferSize size of buffer
   * @throws IOException if thrown by calls to read/write on streams.
   */
  public static void transferStream(final Object from, final Object to, final int bufferSize) throws IOException {
    InputStream in = null;
    OutputStream out = null;

    try {
      in = getInputStream(from);
      out = getOutputStream(to);
      byte[] buffer = new byte[bufferSize];
      int read;
      while ((read = in.read(buffer, 0, bufferSize)) != -1) {
        out.write(buffer, 0, read);
      }
      closeStream(in);
      closeStream(out);
    } catch (Exception e) {
      closeStream(in);
      closeStream(out);
      if (e instanceof IOException) {
        throw (IOException) e;
      }
    }
  }

  /**
   * Close an InputStream or OutputStream.
   *
   * @param stream stream to close.
   */
  public static void closeStream(final Object stream) {
    try {
      if (stream instanceof OutputStream) {
        OutputStream out = (OutputStream) stream;
        try {
          out.flush();
        } finally {
          out.close();
        }
      } else if (stream instanceof InputStream) {
        ((InputStream) stream).close();
      }
    } catch (Exception e) {
      // ignore
    }
  }

  /**
   * An InputStream that ignores calls to close.
   *
   * Used for methods that automatically close a stream at EOF, even though it is
   * undesirable for the stream to be closed, such as when using a ZipInputStream.
   *
   * @author jmfee
   *
   */
  public static class UnclosableInputStream extends FilterInputStream {

    /**
     * Create a new UnclosableInputStream object.
     *
     * @param in the InputStream to wrap.
     */
    public UnclosableInputStream(final InputStream in) {
      super(in);
    }

    /**
     * Does not close stream.
     */
    public void close() {
      // ignore
    }
  }

  /**
   * An OutputStream that ignores calls to close.
   *
   * Used for methods that automatically close a stream, even though it is
   * undesirable for the stream to be closed, such as when using a
   * ZipOutputStream.
   *
   * @author jmfee
   *
   */
  public static class UnclosableOutputStream extends FilterOutputStream {

    /**
     * Create a new UnclosableOutputStream object.
     *
     * @param out the OutputStream to wrap.
     */
    public UnclosableOutputStream(final OutputStream out) {
      super(out);
    }

    /**
     * Flush written content, but does not close stream.
     */
    public void close() throws IOException {
      out.flush();
      // otherwise ignore
    }
  }

}