package org.springframework.roo.support.util; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.Collection; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.springframework.roo.support.ant.AntPathMatcher; import org.springframework.roo.support.ant.PathMatcher; /** * Utilities for handling {@link File} instances. * * @author Ben Alex * @since 1.0 */ public final class FileUtils { private static final String 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 ESCAPED_BACKSLASH = "\\\\"; private static final PathMatcher PATH_MATCHER; static { PATH_MATCHER = new AntPathMatcher(); ((AntPathMatcher) PATH_MATCHER).setPathSeparator(File.separator); } /** * Returns the given file system path minus its last element * * @param fileIdentifier * @return * @since 1.2.0 */ public static String backOneDirectory(String fileIdentifier) { fileIdentifier = StringUtils.removeEnd(fileIdentifier, File.separator); fileIdentifier = fileIdentifier.substring(0, fileIdentifier.lastIndexOf(File.separator)); return StringUtils.removeEnd(fileIdentifier, File.separator); } /** * 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) { Validate.notNull(source, "Source directory required"); Validate.notNull(destination, "Destination directory required"); Validate.isTrue(source.exists(), "Source directory '%s' must exist", source); Validate.isTrue(source.isDirectory(), "Source directory '%s' must be a directory", source); if (destination.exists()) { Validate.isTrue(destination.isDirectory(), "Destination directory '%s' must be a directory", destination); } 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 { org.apache.commons.io.FileUtils.copyFile(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; } /** * 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) { Validate.notNull(path); return StringUtils.stripEnd(path, File.separator) + File.separatorChar; } /** * 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; } /** * 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 = StringUtils.stripEnd(fileIdentifier, File.separator); if (new File(fileIdentifier).isDirectory()) { return fileIdentifier; } return backOneDirectory(fileIdentifier); } // /** * 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 NullPointerException if the given file cannot be found */ public static InputStream getInputStream(final Class<?> loadingClass, final String filename) { final InputStream inputStream = loadingClass.getResourceAsStream(filename); Validate.notNull(inputStream, "Could not locate '%s' in classpath of %s", filename, loadingClass.getName()); return inputStream; } /** * 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) { Validate.notNull(loadingClass, "Loading class required"); Validate.notBlank(relativeFilename, "Filename required"); Validate.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; } /** * 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) { Validate.notEmpty(pathElements); return StringUtils.join(pathElements, File.separator); } /** * 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) { Validate.notEmpty(pathElements); return getSystemDependentPath(Arrays.asList(pathElements)); } // /** * 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) { Validate.notBlank(antPattern, "Ant pattern required"); Validate.notBlank(canonicalPath, "Canonical path required"); return PATH_MATCHER.match(antPattern, canonicalPath); } /** * Constructor is private to prevent instantiation * * @since 1.2.0 */ private FileUtils() {} }