/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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.google.dart.tools.core.utilities.io;
import com.google.common.io.CharStreams;
import com.google.dart.engine.AnalysisEngine;
import com.google.dart.engine.utilities.logging.Logger;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.runtime.CoreException;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.net.URL;
/**
* The class <code>FileUtilities</code> implements utility methods used to create and manipulate
* files.
*
* @coverage dart.tools.core.utilities
*/
public class FileUtilities {
/**
* Copy over all the files and directories contained in the source directory to the target
* directory.
*
* @param sourceDirectory the directory whose contents are to be copied
* @param targetDirectory the directory to which the contents are to be copied
*/
public static void copyDirectoryContents(File sourceDirectory, File targetDirectory)
throws IOException {
File[] children;
File sourceFile, targetFile;
if (!sourceDirectory.exists()) {
throw new IllegalArgumentException(
"sourceDirectory does not exist: " + sourceDirectory.getAbsolutePath()); //$NON-NLS-1$
} else if (!sourceDirectory.isDirectory()) {
throw new IllegalArgumentException(
"sourceDirectory is not a directory: " + sourceDirectory.getAbsolutePath()); //$NON-NLS-1$
}
if (!targetDirectory.exists()) {
targetDirectory.mkdirs();
} else if (!targetDirectory.isDirectory()) {
throw new IllegalArgumentException(
"targetDirectory is not a directory: " + targetDirectory.getAbsolutePath()); //$NON-NLS-1$
}
children = sourceDirectory.listFiles();
for (int i = 0; i < children.length; i++) {
sourceFile = children[i];
targetFile = new File(targetDirectory, sourceFile.getName());
if (sourceFile.isDirectory()) {
copyDirectoryContents(sourceFile, targetFile);
} else {
copyFile(sourceFile, targetFile);
}
}
}
/**
* Copy the contents of the given input file to the given output file.
*
* @param input the input file from which the contents are to be read
* @param output the output file to which the contents are to be written
* @throws IOException if the files cannot be either read or written
*/
public static void copyFile(File input, File output) throws IOException {
copyFile(new FileInputStream(input), new FileOutputStream(output));
}
/**
* Copy all of the bytes from the given input stream to the given output stream. Both streams will
* be closed after the operation.
*
* @param input the input stream from which bytes are to be read
* @param output the output stream to which bytes are to be written
* @throws IOException if the bytes cannot be either read or written
*/
public static void copyFile(InputStream input, OutputStream output) throws IOException {
int bufferSize, readSize;
byte[] buffer;
bufferSize = 8192;
buffer = new byte[bufferSize];
try {
readSize = input.read(buffer, 0, bufferSize);
while (readSize >= 0) {
output.write(buffer, 0, readSize);
readSize = input.read(buffer, 0, bufferSize);
}
} finally {
try {
input.close();
} catch (IOException exception) {
// ignore failures to close the input
}
try {
output.close();
} catch (IOException exception) {
// ignore failures to close the output
}
}
}
/**
* Copy the contents of the given input file to the given output file.
*
* @param input the input file from which the contents are to be read
* @param output the output file to which the contents are to be written
* @throws IOException if the files cannot be either read or written
*/
public static void copyFile(URL input, File output) throws IOException {
copyFile(input.openStream(), new FileOutputStream(output));
}
/**
* Create the given file, including any parent directories that do not already exist.
*
* @param file the file to be created
* @throws IOException if the file could not be created
*/
public static void create(File file) throws IOException {
File parent;
parent = file.getParentFile();
if (parent != null && !parent.exists()) {
if (!parent.mkdirs()) {
throw new IOException("Could not create directory " + parent.getAbsolutePath()); //$NON-NLS-1$
}
}
if (file.isDirectory()) {
if (!file.mkdir()) {
throw new IOException("Could not create directory " + file.getAbsolutePath()); //$NON-NLS-1$
}
} else {
file.createNewFile();
}
}
/**
* Given some {@link File} valid absolute directory path on disk that does or doesn't exist yet,
* this method will create the directories no disk that don't yet exist.
*
* @param directory some valid absolute directory path on disk that does or doesn't exist yet
*/
public static void createDirectory(File directory) {
if (directory == null) {
return;
} else if (directory.isDirectory()) {
return;
}
createDirectory(directory.getParentFile());
directory.mkdir();
}
/**
* Delete the given file or directory. If the argument is a directory, then the contents of the
* directory will be deleted before the directory itself is deleted.
*
* @param file the file or directory to be deleted
*/
public static void delete(File file) {
if (file.isDirectory()) {
safelyDeleteContents(file);
}
file.delete();
}
/**
* Delete the contents of the given directory without deleting the directory itself.
*
* @param directory the directory whose contents are to be deleted
* @throws IllegalArgumentException if the argument is not a directory
*/
public static void deleteContents(File directory) {
if (!directory.isDirectory()) {
throw new IllegalArgumentException(
"Cannot delete file contents: \"" + directory.getAbsolutePath() + "\""); //$NON-NLS-1$
}
safelyDeleteContents(directory);
}
/**
* Return a string that contains all of the characters in the given string that can be included in
* a valid file name.
*
* @param string the string used to build the valid file name
* @return a valid file name that is as close to the given string as possible
*/
public static String deriveFileName(String string) {
if (string == null) {
return null;
}
StringBuilder builder = new StringBuilder();
int length = string.length();
for (int i = 0; i < length; i++) {
char currentChar = string.charAt(i);
if (isValidFileNameChar(currentChar)) {
builder.append(currentChar);
}
}
return builder.toString();
}
/**
* Ensure that the given file exists and is executable. If it exists but is not executable, then
* make it executable and log that it was necessary for us to do so.
* <p>
* Originally copied from the engine project
*
* @return {@code true} if the file exists and is executable, else {@code false}.
*/
public static boolean ensureExecutable(File file) {
if (file == null || !file.exists()) {
return false;
}
if (!file.canExecute()) {
Logger logger = AnalysisEngine.getInstance().getLogger();
if (!makeExecutable(file)) {
logger.logError(file + " cannot be made executable");
return false;
}
logger.logError(file + " was not executable");
}
return true;
}
/**
* Return the base name of the given file. The base name is the portion of the name that occurs
* before the period or extension when a file name is assumed to be of the form
* <code>baseName '.' extension</code>.
* <p>
* Originally copied from the engine project
*
* @return the base name of the given file
*/
public static String getBaseName(File file) {
String name;
int index;
name = file.getName();
index = name.lastIndexOf('.');
if (index >= 0) {
return name.substring(0, index);
}
return name;
}
/**
* Return the contents of the given file, interpreted as a string.
*
* @param file the file whose contents are to be returned
* @return the contents of the given file, interpreted as a string
* @throws IOException if the file contents could not be read
*/
public static String getContents(File file, String charsetName) throws IOException {
Reader streamReader = new InputStreamReader(new FileInputStream(file), charsetName);
BufferedReader reader = new BufferedReader(streamReader);
return getContents(reader);
}
/**
* Return the contents of the given reader, interpreted as a string. The {@link Reader} will be
* closed.
*
* @param reader the reader whose contents are to be returned
* @return the contents of the given reader, interpreted as a string
* @throws IOException if the reader could not be read
*/
public static String getContents(Reader reader) throws IOException {
try {
return CharStreams.toString(reader);
} finally {
reader.close();
}
}
/**
* @return the contents of the given Dart file with UTF-8 encoding.
*/
public static String getDartContents(File file) throws IOException {
return getContents(file, "UTF-8");
}
/**
* Return the extension of the given file. The extension is the portion of the name that occurs
* after the final period when a file name is assumed to be of the form
* <code>baseName '.' extension</code>.
*
* @return the extension of the given file
*/
public static String getExtension(File file) {
String name;
int index;
name = file.getName();
index = name.lastIndexOf('.');
if (index >= 0) {
return name.substring(index + 1);
}
return "";
}
/**
* If the file with given path exists, and its canonical path differs from the given path only in
* case, return the path with the same case as the {@link File} in the file system.
* <p>
* According to specification it is OK to use a different case in URI, as long as embedding allows
* this. But Eclipse LTK (refactoring framework) is not happy. We need to convert such URI to
* canonical, at least during refactoring.
*/
public static String getFileSystemCase(String path) {
File file = new File(path);
if (file.exists()) {
try {
String absolutePath = file.getAbsolutePath();
String canonicalPath = file.getCanonicalPath();
if (StringUtils.equalsIgnoreCase(absolutePath, canonicalPath)) {
return canonicalPath;
}
} catch (Throwable e) {
}
}
return path;
}
/**
* Return a directory with the given name in the given base directory. If the directory did not
* already exist it will be created. If there is a file of the same name in the base directory,
* then the directory name will be made unique by appending an integer to the base name.
*
* @return a directory with the given name in the given base directory
* @throws SecurityException if the directory cannot be accessed or created
*/
public static File getOrCreateDirectory(File baseDirectory, String baseName) {
File directory;
int index;
directory = new File(baseDirectory, baseName);
index = 1;
while (directory.exists() && !directory.isDirectory()) {
directory = new File(baseDirectory, baseName + index);
index++;
}
if (!directory.exists()) {
directory.mkdir();
}
return directory;
}
/**
* Return a file in the given directory whose name is composed from the given base name and
* extension (which should include the period), but which does not currently exist.
*
* @param directory the directory that should contain the file
* @param baseName the base name of the file
* @param extension the extension used for the file
* @return a unique file that can be created without overwriting any other file
*/
public static File getUniqueFile(File directory, String baseName, String extension) {
File file;
int index;
file = new File(directory, baseName + extension);
index = 1;
while (file.exists()) {
file = new File(directory, baseName + index + extension);
index++;
}
return file;
}
/**
* Returns whether the given file is a symlinked file.
*
* @param file
* @return
* @throws CoreException
* @throws IOException
*/
public static boolean isLinkedFile(File file) throws CoreException {
IFileStore fileStore = EFS.getStore(file.toURI());
IFileInfo info = fileStore.fetchInfo();
return info.getAttribute(EFS.ATTRIBUTE_SYMLINK);
}
/**
* Return <code>true</code> if the given parent directory is either the same as or a parent of the
* given child directory.
*
* @param parentDirectory the directory that might be a parent of the child directory
* @param childDirectory the directory that might be a child of the parent directory
* @return <code>true</code> if the given parent directory is a parent of the given child
* directory
*/
public static boolean isParentOf(File parentDirectory, File childDirectory) {
File directory = childDirectory;
while (directory != null) {
if (parentDirectory.equals(directory)) {
return true;
}
directory = directory.getParentFile();
}
return false;
}
/**
* Attempt to make the given file executable.
*
* @param file the file to be made executable
* @return {@code true} if the file is executable
*/
public static boolean makeExecutable(File file) {
// Try to make the file executable for all users.
if (file.setExecutable(true, false)) {
return true;
}
// If that fails, then try to make it executable for the current user.
return file.setExecutable(true, true);
}
/**
* Overwrite the contents of the given file to the given contents.
*
* @param file the file whose contents are to be written
* @param contents the new contents for the file
* @throws IOException if the file contents could not be written
*/
public static void setContents(File file, String contents) throws IOException {
FileWriter fileWriter = null;
BufferedWriter writer;
try {
fileWriter = new FileWriter(file);
writer = new BufferedWriter(fileWriter);
writer.write(contents);
writer.flush();
} finally {
if (fileWriter != null) {
fileWriter.close();
}
}
}
/**
* Return <code>true</code> if the given character can be included in a valid file name.
*
* @param character the character being tested
* @return <code>true</code> if the given character can be included in a valid file name
*/
private static boolean isValidFileNameChar(char character) {
if (Character.isWhitespace(character)) {
return false;
}
String invalidChars;
// if (OS.isWindows()) {
invalidChars = "\\/:*?\"<>|";
// } else if (OS.isMacOSX()) {
// invalidChars = "/:";
// } else { // assume Unix/Linux
// invalidChars = "/";
// }
return !((invalidChars.indexOf(character) >= 0) // OS-invalid
|| (character < '\u0020') // ctrls
|| (character > '\u007e' && character < '\u00a0')); // ctrls
}
/**
* Delete the contents of the given directory, given that we know it is a directory.
*
* @param directory the directory whose contents are to be deleted
*/
private static void safelyDeleteContents(File directory) {
File[] children;
children = directory.listFiles();
for (int i = 0; i < children.length; i++) {
delete(children[i]);
}
}
/**
* Disallow the creation of instances of this class.
*/
private FileUtilities() {
}
}