/*
* Copyright 2011 Eric F. Savage, code@efsavage.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ajah.util.io.file;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.ajah.util.AjahUtils;
/**
* Utilities for basic file operations.
*
* @author <a href="http://efsavage.com">Eric F. Savage</a>,
* <a href="mailto:code@efsavage.com">code@efsavage.com</a>.
*/
public class FileUtils {
/**
* UTF-8.
*/
public static final String UTF8 = "UTF-8";
private static Logger log = Logger.getLogger(FileUtils.class.getName());
/**
* Copies a file to another file by reading and writing the data.
*
* @param file
* The file to read from.
* @param newFile
* The file to write to.
* @return The number of bytes written.
*/
public static long copyFile(final File file, final File newFile) {
// TODO Write to temp file then rename
try (InputStream in = new FileInputStream(file)) {
try (OutputStream out = new FileOutputStream(newFile)) {
long bytes = 0;
int read;
do {
final byte[] block = new byte[1024];
read = in.read(block);
if (read < 0) {
break;
}
bytes += read;
out.write(block, 0, read);
} while (read >= 0);
log.fine("Copied " + bytes + " bytes");
return bytes;
}
} catch (final IOException e) {
if (e.getMessage().endsWith("(Access is denied)")) {
// log.info(e.getMessage());
} else if (e.getMessage().equals("The process cannot access the file because another process has locked a portion of the file")) {
// log.info(e.getMessage());
} else {
log.warning(file.getAbsolutePath());
log.log(Level.WARNING, e.getMessage(), e);
}
return -1;
}
}
/**
* Reads a file to a UTF-8 string.
*
* @param file
* The file to read.
* @return The contents of the file as a string.
* @throws IOException
* If the file could not be read.
*/
public static String readFile(final File file) throws IOException {
try {
return readFile(file, UTF8);
} catch (final UnsupportedEncodingException e) {
throw new IllegalArgumentException("UTF-8 encoding is not supported?!");
}
}
/**
* Reads a file to a string, with the specified character set.
*
* @param file
* The file to read
* @param charSet
* The character set to use when constructing the string.
* @return The contents of the file as a string.
* @throws IOException
* If the file could not be read.
* @throws UnsupportedEncodingException
* If the encoding is not supported.
*/
public static String readFile(final File file, final String charSet) throws UnsupportedEncodingException, IOException {
return new String(readFileAsBytes(file), charSet);
}
/**
* Reads a file and returns the lines of it as a list of strings. Handles
* opening a closing the file.
*
* @param fileName
* The name of the file to load, required.
* @return List of lines in the file.
* @throws IOException
* If the file could not be read.
*/
public static String readFile(final String fileName) throws IOException {
if (fileName == null || fileName.length() < 1) {
throw new IllegalArgumentException("Filename may not be null or empty");
}
final File file = new File(fileName);
return readFile(file);
}
/**
* Reads a file into a byte array.
*
* @param file
* The file to read, required.
* @return The data in the file as a byte array.
* @throws IOException
* If the file could not be read.
*/
public static byte[] readFileAsBytes(final File file) throws IOException {
AjahUtils.requireParam(file, "file");
if (!file.exists()) {
throw new FileNotFoundException(file.getAbsolutePath());
}
try (FileInputStream in = new FileInputStream(file)) {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final DataOutputStream dos = new DataOutputStream(baos);
final byte[] data = new byte[4096];
int count = in.read(data);
while (count != -1) {
dos.write(data, 0, count);
count = in.read(data);
}
return baos.toByteArray();
}
}
/**
* Reads a file and returns the lines of it as a list of strings. Handles
* opening a closing the file. If there are any errors other than that the
* file does not exist, will return an empty or partial list. Will ignore
* any line that starts with "#".
*
* @param fileName
* The name of the file to load, required.
* @return List of lines in the file.
* @throws FileNotFoundException
* If the file does not exist.
*/
public static List<String> readFileAsLines(final String fileName) throws FileNotFoundException {
if (fileName == null || fileName.length() < 1) {
throw new IllegalArgumentException("Filename may not be null or empty");
}
final File file = new File(fileName);
if (!file.exists()) {
throw new FileNotFoundException(file.getAbsolutePath());
}
final List<String> data = new ArrayList<>();
try (BufferedReader in = new BufferedReader(new FileReader(file))) {
while (in.ready()) {
final String line = in.readLine();
if (line != null && !line.startsWith("#") && line.length() > 0) {
data.add(line);
}
}
} catch (final IOException e) {
log.log(Level.SEVERE, e.getMessage(), e);
}
log.log(Level.FINER, data.size() + " elements loaded.");
return data;
}
/**
* Reads a file as a long value. If there are any errors, returns a default
* value.
*
* @param file
* The file to read.
* @param defaultValue
* The value to return if no valid value is available.
* @return The contents of the file as a long, or the defaultValue.
*/
public static Long readFileAsLong(final File file, final Long defaultValue) {
try {
return Long.valueOf(readFile(file));
} catch (final NumberFormatException e) {
log.warning(e.getMessage());
return defaultValue;
} catch (final FileNotFoundException e) {
log.warning(e.getMessage());
return defaultValue;
} catch (final IOException e) {
log.warning(e.getMessage());
return defaultValue;
}
}
/**
* Writes a byte array to a {@link File}. Will create parent directories for
* the file if necessary. Will overwite existing files.
*
* @param file
* The file to write to, required.
* @param data
* The data to write.
* @return The number of bytes read/written.
* @throws IOException
*/
public static long write(final File file, final byte[] data) throws IOException {
return write(file, data, true);
}
/**
* Writes a byte array to a {@link File}. Will create parent directories for
* the file if necessary.
*
* @param file
* The file to write to, required.
* @param data
* The data to write.
* @param overwrite
* Overwrite the file if it exists?
* @return The number of bytes read/written.
* @throws IOException
*/
public static long write(final File file, final byte[] data, final boolean overwrite) throws IOException {
// TODO Write to temp file then rename
AjahUtils.requireParam(data, "data");
if (file.exists() && !overwrite) {
log.fine(file.getAbsolutePath() + " exists and overwrite is false, aborting");
return 0;
}
if (file.getParentFile() != null) {
file.getParentFile().mkdirs();
}
try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
long total = 0;
for (int i = 0; i < data.length; i++) {
// TODO Is this faster or should we chunk it?
total++;
out.write(data, i, 1);
}
log.finest("Wrote " + total + " bytes to " + file.getAbsolutePath());
return total;
}
}
/**
* Writes a collection of strings to a file.
*
* @param file
* The file to write to.
* @param lines
* The lines to write. Note that the strings should not already
* have line-ending characters as they will be added
* automatically.
* @return The number of lines written.
* @throws IOException
* If the file could not be written to.
*/
public static int write(final File file, final Collection<String> lines) throws IOException {
// TODO Write to temp file then rename
if (file.getParentFile() != null) {
file.getParentFile().mkdirs();
}
try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
for (final String line : lines) {
out.write(line.getBytes());
out.write('\n');
}
log.finest("Wrote " + lines.size() + " lines");
return lines.size();
}
}
/**
* Writes an {@link InputStream} to a {@link File}. Will create parent
* directories for the file if necessary.
*
* @param file
* The file to write to, required.
* @param in
* The stream to read from.
* @return The number of bytes read/written.
* @throws IOException
*/
public static long write(final File file, final InputStream in) throws IOException {
// TODO Write to temp file then rename
AjahUtils.requireParam(file, "file");
if (file.getParentFile() != null) {
file.getParentFile().mkdirs();
}
try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
final byte buf[] = new byte[1024];
int read;
long total = 0;
while ((read = in.read(buf)) != -1) {
total += read;
out.write(buf, 0, read);
}
log.finest("Wrote " + total + " bytes to " + file.getAbsolutePath());
return total;
}
}
/**
* Writes a string to a file. Overwrites existing files.
*
* @param file
* The file to write to.
* @param string
* The string to write.
* @return The number of bytes written.
* @throws IOException
* If the file could not be written to.
*/
public static int write(final File file, final String string) throws IOException {
return write(file, string, true);
}
/**
* Writes a string to a file.
*
* @param file
* The file to write to.
* @param string
* The string to write.
* @param overwrite
* Overwrite the file if it exists?
* @return The number of bytes written.
* @throws IOException
* If the file could not be written to.
*/
public static int write(final File file, final String string, final boolean overwrite) throws IOException {
// TODO Write to temp file then rename
if (file.exists() && !overwrite) {
log.fine(file.getAbsolutePath() + " exists and overwrite is false, aborting");
return 0;
}
if (file.getParentFile() != null) {
file.getParentFile().mkdirs();
}
try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
out.write(string.getBytes(UTF8));
log.fine("Wrote " + string.length() + " bytes");
return string.length();
}
}
}