/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.parse;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
/**
* General file manipulation utilities.
*/
/** package */ class ParseFileUtils {
/**
* The number of bytes in a kilobyte.
*/
public static final long ONE_KB = 1024;
/**
* The number of bytes in a megabyte.
*/
public static final long ONE_MB = ONE_KB * ONE_KB;
/**
* The file copy buffer size (30 MB)
*/
private static final long FILE_COPY_BUFFER_SIZE = ONE_MB * 30;
/**
* Reads the contents of a file into a byte array.
* The file is always closed.
*
* @param file the file to read, must not be null
* @return the file contents, never null
* @throws IOException in case of an I/O error
* @since Commons IO 1.1
*/
public static byte[] readFileToByteArray(File file) throws IOException {
InputStream in = null;
try {
in = openInputStream(file);
return ParseIOUtils.toByteArray(in);
} finally {
ParseIOUtils.closeQuietly(in);
}
}
//-----------------------------------------------------------------------
/**
* Opens a {@link FileInputStream} for the specified file, providing better
* error messages than simply calling new FileInputStream(file)
.
*
* At the end of the method either the stream will be successfully opened, * or an exception will have been thrown. *
* An exception is thrown if the file does not exist.
* An exception is thrown if the file object exists but is a directory.
* An exception is thrown if the file exists but cannot be read.
*
* @param file the file to open for input, must not be null
* @return a new {@link FileInputStream} for the specified file
* @throws FileNotFoundException if the file does not exist
* @throws IOException if the file object is a directory
* @throws IOException if the file cannot be read
* @since Commons IO 1.3
*/
public static FileInputStream openInputStream(File file) throws IOException {
if (file.exists()) {
if (file.isDirectory()) {
throw new IOException("File '" + file + "' exists but is a directory");
}
if (!file.canRead()) {
throw new IOException("File '" + file + "' cannot be read");
}
} else {
throw new FileNotFoundException("File '" + file + "' does not exist");
}
return new FileInputStream(file);
}
/**
* Writes a byte array to a file creating the file if it does not exist.
*
* NOTE: As from v1.3, the parent directories of the file will be created * if they do not exist. * * @param file the file to write to * @param data the content to write to the file * @throws IOException in case of an I/O error * @since Commons IO 1.1 */ public static void writeByteArrayToFile(File file, byte[] data) throws IOException { OutputStream out = null; try { out = openOutputStream(file); out.write(data); } finally { ParseIOUtils.closeQuietly(out); } } //----------------------------------------------------------------------- /** * Opens a {@link FileOutputStream} for the specified file, checking and * creating the parent directory if it does not exist. *
* At the end of the method either the stream will be successfully opened, * or an exception will have been thrown. *
* The parent directory will be created if it does not exist.
* The file will be created if it does not exist.
* An exception is thrown if the file object exists but is a directory.
* An exception is thrown if the file exists but cannot be written to.
* An exception is thrown if the parent directory cannot be created.
*
* @param file the file to open for output, must not be null
* @return a new {@link FileOutputStream} for the specified file
* @throws IOException if the file object is a directory
* @throws IOException if the file cannot be written to
* @throws IOException if a parent directory needs creating but that fails
* @since Commons IO 1.3
*/
public static FileOutputStream openOutputStream(File file) throws IOException {
if (file.exists()) {
if (file.isDirectory()) {
throw new IOException("File '" + file + "' exists but is a directory");
}
if (!file.canWrite()) {
throw new IOException("File '" + file + "' cannot be written to");
}
} else {
File parent = file.getParentFile();
if (parent != null && !parent.exists()) {
if (!parent.mkdirs()) {
throw new IOException("File '" + file + "' could not be created");
}
}
}
return new FileOutputStream(file);
}
/**
* Moves a file.
*
* When the destination file is on another file system, do a "copy and delete". * * @param srcFile the file to be moved * @param destFile the destination file * @throws NullPointerException if source or destination is {@code null} * @throws IOException if source or destination is invalid * @throws IOException if an IO error occurs moving the file * @since 1.4 */ public static void moveFile(final File srcFile, final File destFile) throws IOException { if (srcFile == null) { throw new NullPointerException("Source must not be null"); } if (destFile == null) { throw new NullPointerException("Destination must not be null"); } if (!srcFile.exists()) { throw new FileNotFoundException("Source '" + srcFile + "' does not exist"); } if (srcFile.isDirectory()) { throw new IOException("Source '" + srcFile + "' is a directory"); } if (destFile.exists()) { throw new IOException("Destination '" + destFile + "' already exists"); } if (destFile.isDirectory()) { throw new IOException("Destination '" + destFile + "' is a directory"); } final boolean rename = srcFile.renameTo(destFile); if (!rename) { copyFile( srcFile, destFile ); if (!srcFile.delete()) { ParseFileUtils.deleteQuietly(destFile); throw new IOException("Failed to delete original file '" + srcFile + "' after copy to '" + destFile + "'"); } } } /** * Copies a file to a new location preserving the file date. *
* This method copies the contents of the specified source file to the * specified destination file. The directory holding the destination file is * created if it does not exist. If the destination file exists, then this * method will overwrite it. *
* Note: This method tries to preserve the file's last * modified date/times using {@link File#setLastModified(long)}, however * it is not guaranteed that the operation will succeed. * If the modification operation fails, no indication is provided. * * @param srcFile an existing file to copy, must not be {@code null} * @param destFile the new file, must not be {@code null} * * @throws NullPointerException if source or destination is {@code null} * @throws IOException if source or destination is invalid * @throws IOException if an IO error occurs during copying * @throws IOException if the output file length is not the same as the input file length after the copy completes * @see #copyFile(File, File, boolean) */ public static void copyFile(final File srcFile, final File destFile) throws IOException { copyFile(srcFile, destFile, true); } /** * Copies a file to a new location. *
* This method copies the contents of the specified source file * to the specified destination file. * The directory holding the destination file is created if it does not exist. * If the destination file exists, then this method will overwrite it. *
* Note: Setting preserveFileDate
to
* {@code true} tries to preserve the file's last modified
* date/times using {@link File#setLastModified(long)}, however it is
* not guaranteed that the operation will succeed.
* If the modification operation fails, no indication is provided.
*
* @param srcFile an existing file to copy, must not be {@code null}
* @param destFile the new file, must not be {@code null}
* @param preserveFileDate true if the file date of the copy
* should be the same as the original
*
* @throws NullPointerException if source or destination is {@code null}
* @throws IOException if source or destination is invalid
* @throws IOException if an IO error occurs during copying
* @throws IOException if the output file length is not the same as the input file length after the copy completes
* @see #doCopyFile(File, File, boolean)
*/
public static void copyFile(final File srcFile, final File destFile,
final boolean preserveFileDate) throws IOException {
if (srcFile == null) {
throw new NullPointerException("Source must not be null");
}
if (destFile == null) {
throw new NullPointerException("Destination must not be null");
}
if (!srcFile.exists()) {
throw new FileNotFoundException("Source '" + srcFile + "' does not exist");
}
if (srcFile.isDirectory()) {
throw new IOException("Source '" + srcFile + "' exists but is a directory");
}
if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath())) {
throw new IOException("Source '" + srcFile + "' and destination '" + destFile + "' are the same");
}
final File parentFile = destFile.getParentFile();
if (parentFile != null) {
if (!parentFile.mkdirs() && !parentFile.isDirectory()) {
throw new IOException("Destination '" + parentFile + "' directory cannot be created");
}
}
if (destFile.exists() && !destFile.canWrite()) {
throw new IOException("Destination '" + destFile + "' exists but is read-only");
}
doCopyFile(srcFile, destFile, preserveFileDate);
}
/**
* Internal copy file method.
* This caches the original file length, and throws an IOException
* if the output file length is different from the current input file length.
* So it may fail if the file changes size.
* It may also fail with "IllegalArgumentException: Negative size" if the input file is truncated part way
* through copying the data and the new file size is less than the current position.
*
* @param srcFile the validated source file, must not be {@code null}
* @param destFile the validated destination file, must not be {@code null}
* @param preserveFileDate whether to preserve the file date
* @throws IOException if an error occurs
* @throws IOException if the output file length is not the same as the input file length after the copy completes
* @throws IllegalArgumentException "Negative size" if the file is truncated so that the size is less than the position
*/
private static void doCopyFile(final File srcFile, final File destFile, final boolean preserveFileDate) throws IOException {
if (destFile.exists() && destFile.isDirectory()) {
throw new IOException("Destination '" + destFile + "' exists but is a directory");
}
FileInputStream fis = null;
FileOutputStream fos = null;
FileChannel input = null;
FileChannel output = null;
try {
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
input = fis.getChannel();
output = fos.getChannel();
final long size = input.size(); // TODO See IO-386
long pos = 0;
long count = 0;
while (pos < size) {
final long remain = size - pos;
count = remain > FILE_COPY_BUFFER_SIZE ? FILE_COPY_BUFFER_SIZE : remain;
final long bytesCopied = output.transferFrom(input, pos, count);
if (bytesCopied == 0) { // IO-385 - can happen if file is truncated after caching the size
break; // ensure we don't loop forever
}
pos += bytesCopied;
}
} finally {
ParseIOUtils.closeQuietly(output);
ParseIOUtils.closeQuietly(fos);
ParseIOUtils.closeQuietly(input);
ParseIOUtils.closeQuietly(fis);
}
final long srcLen = srcFile.length(); // TODO See IO-386
final long dstLen = destFile.length(); // TODO See IO-386
if (srcLen != dstLen) {
throw new IOException("Failed to copy full contents from '" +
srcFile + "' to '" + destFile + "' Expected length: " + srcLen +" Actual: " + dstLen);
}
if (preserveFileDate) {
destFile.setLastModified(srcFile.lastModified());
}
}
//-----------------------------------------------------------------------
/**
* Deletes a directory recursively.
*
* @param directory directory to delete
* @throws IOException in case deletion is unsuccessful
*/
public static void deleteDirectory(final File directory) throws IOException {
if (!directory.exists()) {
return;
}
if (!isSymlink(directory)) {
cleanDirectory(directory);
}
if (!directory.delete()) {
final String message =
"Unable to delete directory " + directory + ".";
throw new IOException(message);
}
}
/**
* Deletes a file, never throwing an exception. If file is a directory, delete it and all sub-directories.
*
* The difference between File.delete() and this method are: *
* The difference between File.delete() and this method are: *
* Will not return true if there is a Symbolic Link anywhere in the path, * only if the specific file is. *
* For code that runs on Java 1.7 or later, use the following method instead:
*
* {@code boolean java.nio.file.Files.isSymbolicLink(Path path)}
* @param file the file to check
* @return true if the file is a Symbolic Link
* @throws IOException if an IO error occurs while checking the file
* @since 2.0
*/
public static boolean isSymlink(final File file) throws IOException {
if (file == null) {
throw new NullPointerException("File must not be null");
}
// if (FilenameUtils.isSystemWindows()) {
// return false;
// }
File fileInCanonicalDir = null;
if (file.getParent() == null) {
fileInCanonicalDir = file;
} else {
final File canonicalDir = file.getParentFile().getCanonicalFile();
fileInCanonicalDir = new File(canonicalDir, file.getName());
}
if (fileInCanonicalDir.getCanonicalFile().equals(fileInCanonicalDir.getAbsoluteFile())) {
return false;
} else {
return true;
}
}
//region String
public static String readFileToString(File file, Charset encoding) throws IOException {
return new String(readFileToByteArray(file), encoding);
}
public static String readFileToString(File file, String encoding) throws IOException {
return readFileToString(file, Charset.forName(encoding));
}
public static void writeStringToFile(File file, String string, Charset encoding)
throws IOException {
writeByteArrayToFile(file, string.getBytes(encoding));
}
public static void writeStringToFile(File file, String string, String encoding)
throws IOException {
writeStringToFile(file, string, Charset.forName(encoding));
}
//endregion
//region JSONObject
/**
* Reads the contents of a file into a {@link JSONObject}. The file is always closed.
*/
public static JSONObject readFileToJSONObject(File file) throws IOException, JSONException {
String content = readFileToString(file, "UTF-8");
return new JSONObject(content);
}
/**
* Writes a {@link JSONObject} to a file creating the file if it does not exist.
*/
public static void writeJSONObjectToFile(File file, JSONObject json) throws IOException {
ParseFileUtils.writeByteArrayToFile(file, json.toString().getBytes(Charset.forName("UTF-8")));
}
//endregion
}