package vnet.sms.common.shell.springshell.internal.util;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.regex.Pattern;
import vnet.sms.common.shell.springshell.internal.ant.AntPathMatcher;
import vnet.sms.common.shell.springshell.internal.ant.PathMatcher;
/**
* Utilities for handling {@link File} instances.
*
* @author Ben Alex
* @since 1.0
*/
public final class FileUtils {
// Constants
private static final String BACKSLASH = "\\";
private static final String ESCAPED_BACKSLASH = "\\\\";
/**
* The relative file path to the current directory. Should be valid on all
* platforms that Roo supports.
*/
public static final String CURRENT_DIRECTORY = ".";
private static final String WINDOWS_DRIVE_PREFIX = "^[A-Za-z]:";
// Doesn't check for backslash after the colon, since Java has no issues
// with paths like c:/Windows
private static final Pattern WINDOWS_DRIVE_PATH = Pattern
.compile(WINDOWS_DRIVE_PREFIX
+ ".*");
private static final PathMatcher PATH_MATCHER;
static {
PATH_MATCHER = new AntPathMatcher();
((AntPathMatcher) PATH_MATCHER).setPathSeparator(File.separator);
}
/**
* Deletes the specified {@link File}.
*
* <p>
* If the {@link File} refers to a directory, any contents of that directory
* (including other directories) are also deleted.
*
* <p>
* If the {@link File} does not already exist, this method immediately
* returns true.
*
* @param file
* to delete (required; the file may or may not exist)
* @return true if the file is fully deleted, or false if there was a
* failure when deleting
*/
public static boolean deleteRecursively(final File file) {
Assert.notNull(file, "File to delete required");
if (!file.exists()) {
return true;
}
if (file.isDirectory()) {
for (final File f : file.listFiles()) {
if (!deleteRecursively(f)) {
return false;
}
}
}
file.delete();
return true;
}
/**
* Copies the specified source directory to the destination.
*
* <p>
* Both the source must exist. If the destination does not already exist, it
* will be created. If the destination does exist, it must be a directory
* (not a file).
*
* @param source
* the already-existing source directory (required)
* @param destination
* the destination directory (required)
* @param deleteDestinationOnExit
* indicates whether to mark any created destinations for
* deletion on exit
* @return true if the copy was successful
*/
public static boolean copyRecursively(final File source,
final File destination, final boolean deleteDestinationOnExit) {
Assert.notNull(source, "Source directory required");
Assert.notNull(destination, "Destination directory required");
Assert.isTrue(source.exists(), "Source directory '" + source
+ "' must exist");
Assert.isTrue(source.isDirectory(), "Source directory '" + source
+ "' must be a directory");
if (destination.exists()) {
Assert.isTrue(destination.isDirectory(), "Destination directory '"
+ destination + "' must be a directory");
} else {
destination.mkdirs();
if (deleteDestinationOnExit) {
destination.deleteOnExit();
}
}
for (final File s : source.listFiles()) {
final File d = new File(destination, s.getName());
if (deleteDestinationOnExit) {
d.deleteOnExit();
}
if (s.isFile()) {
try {
FileCopyUtils.copy(s, d);
} catch (final IOException ioe) {
return false;
}
} else {
// It's a sub-directory, so copy it
d.mkdir();
if (!copyRecursively(s, d, deleteDestinationOnExit)) {
return false;
}
}
}
return true;
}
/**
* Checks if the provided fileName denotes an absolute path on the file
* system. On Windows, this includes both paths with and without drive
* letters, where the latter have to start with '\'. No check is performed
* to see if the file actually exists!
*
* @param fileName
* name of a file, which could be an absolute path
* @return true if the fileName looks like an absolute path for the current
* OS
*/
public static boolean denotesAbsolutePath(final String fileName) {
if (OsUtils.isWindows()) {
// first check for drive letter
if (WINDOWS_DRIVE_PATH.matcher(fileName).matches()) {
return true;
}
}
return fileName.startsWith(File.separator);
}
/**
* Returns the part of the given path that represents a directory, in other
* words the given path if it's already a directory, or the parent directory
* if it's a file.
*
* @param fileIdentifier
* the path to parse (required)
* @return see above
* @since 1.2.0
*/
public static String getFirstDirectory(String fileIdentifier) {
fileIdentifier = removeTrailingSeparator(fileIdentifier);
if (new File(fileIdentifier).isDirectory()) {
return fileIdentifier;
}
return backOneDirectory(fileIdentifier);
}
/**
* Returns the given file system path minus its last element
*
* @param fileIdentifier
* @return
* @since 1.2.0
*/
public static String backOneDirectory(String fileIdentifier) {
fileIdentifier = removeTrailingSeparator(fileIdentifier);
fileIdentifier = fileIdentifier.substring(0,
fileIdentifier.lastIndexOf(File.separator));
return removeTrailingSeparator(fileIdentifier);
}
/**
* Removes any trailing {@link File#separator}s from the given path
*
* @param path
* the path to modify (can be <code>null</code>)
* @return the modified path
* @since 1.2.0
*/
public static String removeTrailingSeparator(String path) {
while ((path != null) && path.endsWith(File.separator)) {
path = StringUtils.removeSuffix(path, File.separator);
}
return path;
}
/**
* Indicates whether the given canonical path matches the given Ant-style
* pattern
*
* @param antPattern
* the pattern to check against (can't be blank)
* @param canonicalPath
* the path to check (can't be blank)
* @return see above
* @since 1.2.0
*/
public static boolean matchesAntPath(final String antPattern,
final String canonicalPath) {
Assert.hasText(antPattern, "Ant pattern required");
Assert.hasText(canonicalPath, "Canonical path required");
return PATH_MATCHER.match(antPattern, canonicalPath);
}
/**
* Removes any leading or trailing {@link File#separator}s from the given
* path.
*
* @param path
* the path to modify (can be <code>null</code>)
* @return the path, modified as above, or <code>null</code> if
* <code>null</code> was given
* @since 1.2.0
*/
public static String removeLeadingAndTrailingSeparators(String path) {
if (StringUtils.isBlank(path)) {
return path;
}
while (path.endsWith(File.separator)) {
path = StringUtils.removeSuffix(path, File.separator);
}
while (path.startsWith(File.separator)) {
path = StringUtils.removePrefix(path, File.separator);
}
return path;
}
/**
* Ensures that the given path has exactly one trailing
* {@link File#separator}
*
* @param path
* the path to modify (can't be <code>null</code>)
* @return the normalised path
* @since 1.2.0
*/
public static String ensureTrailingSeparator(final String path) {
Assert.notNull(path);
return removeTrailingSeparator(path) + File.separatorChar;
}
/**
* Returns an operating-system-dependent path consisting of the given
* elements, separated by {@link File#separator}.
*
* @param pathElements
* the path elements from uppermost downwards (can't be empty)
* @return a non-blank string
* @since 1.2.0
*/
public static String getSystemDependentPath(final String... pathElements) {
return getSystemDependentPath(Arrays.asList(pathElements));
}
/**
* Returns an operating-system-dependent path consisting of the given
* elements, separated by {@link File#separator}.
*
* @param pathElements
* the path elements from uppermost downwards (can't be empty)
* @return a non-blank string
* @since 1.2.0
*/
public static String getSystemDependentPath(
final Collection<String> pathElements) {
Assert.notEmpty(pathElements);
return StringUtils.collectionToDelimitedString(pathElements,
File.separator);
}
/**
* Returns the canonical path of the given {@link File}.
*
* @param file
* the file for which to find the canonical path (can be
* <code>null</code>)
* @return the canonical path, or <code>null</code> if a <code>null</code>
* file is given
* @since 1.2.0
*/
public static String getCanonicalPath(final File file) {
if (file == null) {
return null;
}
try {
return file.getCanonicalPath();
} catch (final IOException ioe) {
throw new IllegalStateException(
"Cannot determine canonical path for '" + file + "'", ioe);
}
}
/**
* Returns the platform-specific file separator as a regular expression.
*
* @return a non-blank regex
* @since 1.2.0
*/
public static String getFileSeparatorAsRegex() {
final String fileSeparator = File.separator;
if (fileSeparator.contains(BACKSLASH)) {
// Escape the backslashes
return fileSeparator.replace(BACKSLASH, ESCAPED_BACKSLASH);
}
return fileSeparator;
}
/**
* Determines the path to the requested file, relative to the given class.
*
* @param loadingClass
* the class to whose package the given file is relative
* (required)
* @param relativeFilename
* the name of the file relative to that package (required)
* @return the full classloader-specific path to the file (never
* <code>null</code>)
* @since 1.2.0
*/
public static String getPath(final Class<?> loadingClass,
final String relativeFilename) {
Assert.notNull(loadingClass, "Loading class required");
Assert.hasText(relativeFilename, "Filename required");
Assert.isTrue(!relativeFilename.startsWith("/"),
"Filename shouldn't start with a slash");
// Slashes instead of File.separatorChar is correct here, as these are
// classloader paths (not file system paths)
return "/" + loadingClass.getPackage().getName().replace('.', '/')
+ "/" + relativeFilename;
}
/**
* Loads the given file from the classpath.
*
* @param loadingClass
* the class from whose package to load the file (required)
* @param filename
* the name of the file to load, relative to that package
* (required)
* @return the file's input stream (never <code>null</code>)
* @throws IllegalArgumentException
* if the given file cannot be found
*/
public static File getFile(final Class<?> loadingClass,
final String filename) {
final URL url = loadingClass.getResource(filename);
Assert.notNull(url, "Could not locate '" + filename
+ "' in classpath of " + loadingClass.getName());
try {
return new File(url.toURI());
} catch (final URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Loads the given file from the classpath.
*
* @param loadingClass
* the class from whose package to load the file (required)
* @param filename
* the name of the file to load, relative to that package
* (required)
* @return the file's input stream (never <code>null</code>)
* @throws IllegalArgumentException
* if the given file cannot be found
*/
public static InputStream getInputStream(final Class<?> loadingClass,
final String filename) {
final InputStream inputStream = loadingClass
.getResourceAsStream(filename);
Assert.notNull(inputStream, "Could not locate '" + filename
+ "' in classpath of " + loadingClass.getName());
return inputStream;
}
/**
* Returns the contents of the given File as a String.
*
* @param file
* the file to read from (must be an existing file)
* @return the contents
* @throws IllegalStateException
* in case of I/O errors
* @since 1.2.0
*/
public static String read(final File file) {
try {
return FileCopyUtils.copyToString(file);
} catch (final IOException e) {
throw new IllegalStateException(e);
}
}
/**
* Constructor is private to prevent instantiation
*
* @since 1.2.0
*/
private FileUtils() {
}
}