FileUtils.java

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

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

/**
 * File input, output, content type, and delete utilities.
 *
 * @author jmfee
 *
 */
public class FileUtils {

	/** For looking up file mime types. */
	private static final FileNameMap MIMETYPES = URLConnection.getFileNameMap();

	/**
	 * Get a file mime type based on its filename.
	 *
	 * Calls getContentType(file.getName()).
	 *
	 * @param file a file to get type from
	 * @return String mime type.
	 */
	public static String getContentType(final File file) {
		return getContentType(file.getName());
	}

	/**
	 * Get a file mime type based on its file path extension.
	 *
	 * Uses URLConnection.getFileNameMap().
	 *
	 * @param filename
	 *            file path.
	 * @return String mime type.
	 */
	public static String getContentType(final String filename) {
		return MIMETYPES.getContentTypeFor(filename);
	}

	/**
	 * Read file contents into a byte array.
	 *
	 * @param file
	 *            file to read.
	 * @return byte array of file content.
	 * @throws IOException
	 *             if an error occurs while reading.
	 */
	public static byte[] readFile(final File file) throws IOException {
		return StreamUtils.readStream(file);
	}

	/**
	 * Write a file's content.
	 *
	 * @param file
	 *            file to write.
	 * @param content
	 *            content to write to file.
	 * @throws IOException
	 *            if any errors occur
	 */
	public static void writeFile(final File file, final byte[] content)
			throws IOException {
		StreamUtils.transferStream(content, file);
	}

	/**
	 * Write a file's content atomically.
	 *
	 * @param tempfile
	 *            where file is written.
	 * @param file
	 *            where tempfile is moved after writing.
	 * @param content
	 *            file content to write.
	 * @throws IOException
	 *             if any errors occur.
	 */
	public static void writeFileThenMove(final File tempfile, final File file,
			final byte[] content) throws IOException {
		writeFile(tempfile, content);

		File parent = file.getAbsoluteFile().getParentFile();
		if (!parent.exists()) {
			parent.mkdirs();
		}
		try {
			Files.move(tempfile.toPath(), file.toPath(), StandardCopyOption.ATOMIC_MOVE);
		} catch (AtomicMoveNotSupportedException amnse) {
			Files.move(tempfile.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
		}
	}

	/**
	 * Delete all files recursively within tree.
	 *
	 * @param path
	 *            root of tree to delete.
	 */
	public static void deleteTree(final File path) {
		if (path.exists()) {
			if (path.isDirectory()) {
				File[] files = path.listFiles();
				for (int i = 0; i < files.length; i++) {
					if (files[i].isDirectory()) {
						deleteTree(files[i]);
					} else {
						files[i].delete();
					}
				}
				files = null;
			} else {
				path.delete();
			}
		}

		deleteEmptyParents(path);
	}

	/**
	 * Delete path and any empty parent directories.
	 *
	 * @param path
	 *            directory to start in.
	 */
	public static void deleteEmptyParents(final File path) {
		deleteEmptyParents(path, null);
	}

	/**
	 * Delete path and any empty parent directories up to the stopAt point.
	 *
	 * @param path
	 *            direcotry to start in
	 * @param stopAt
	 *            directory to stop at
	 */
	public static void deleteEmptyParents(final File path, final File stopAt) {
		File parent = path;
		// Loop while parent is not null and we are not yet at the stopAt point
		while (parent != null && !parent.equals(stopAt)) {
			try {
				parent.delete();
				parent = parent.getParentFile();
			} catch (Exception e) {
				e.printStackTrace();
				break;
			}
		}
	}

	/**
	 * Extracts a resource file from within the executing JAR and copies it to
	 * the file system.
	 *
	 * @param rsFile
	 *            Absolute file path (relative to JAR root) of the file to
	 *            extract.
	 * @param fsFile
	 *            File path where to place the extracted file. An absolute path
	 *            here is relative to the file system root. Relative paths for
	 *            this value are relative to the CWD.
	 */
	public static void extractResourceFile(String rsFile, String fsFile) {
		InputStream is = FileUtils.class.getResourceAsStream(rsFile);
		try {
			FileOutputStream os = new FileOutputStream(fsFile);
			try {
				byte[] buf = new byte[1024];
				while (true) {
					int len = is.read(buf);
					if (len < 0) {
						break;
					}
					os.write(buf, 0, len);
				} // END: while

				// Do we have a logger yet?
				// System.err.println("Copied " + rsFile + " from jar file to " +
				// fsFile+" in the file system.  ("+totalCopied+" bytes).");
			} finally {
				StreamUtils.closeStream(os);
			}
		} catch (FileNotFoundException fnf) {
			// Logger?
			System.err.println(fnf.getMessage() + " -- 1");
		} catch (IOException iox) {
			// Logger?
			System.err.println(iox.getMessage() + " -- 2");
		} catch (NullPointerException npx) {
			// A dummy catch
		} finally {
			StreamUtils.closeStream(is);
		}
	}

}