package org.springframework.roo.converters; import java.io.File; import java.util.List; import java.util.regex.Pattern; import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.Validate; import org.springframework.roo.shell.Completion; import org.springframework.roo.shell.Converter; import org.springframework.roo.shell.MethodTarget; /** * {@link Converter} for {@link File}. * * @author Stefan Schmidt * @author Roman Kuzmik * @author Ben Alex * @since 1.0 */ public abstract class FileConverter implements Converter<File> { private static final String HOME_DIRECTORY_SYMBOL = "~"; private static final String home = System.getProperty("user.home"); 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 String convertCompletionBackIntoUserInputStyle(final String originalUserInput, final String completion) { if (denotesAbsolutePath(originalUserInput)) { // Input was originally as a fully-qualified path, so we just keep // the completion in that form return completion; } if (originalUserInput.startsWith(HOME_DIRECTORY_SYMBOL)) { // Input originally started with this symbol, so replace the user's // home directory with it again Validate.notNull(home, "Home directory could not be determined from system properties"); return HOME_DIRECTORY_SYMBOL + completion.substring(home.length()); } // The path was working directory specific, so strip the working // directory given the user never typed it return completion.substring(getWorkingDirectoryAsString().length()); } public File convertFromText(final String value, final Class<?> requiredType, final String optionContext) { return new File(convertUserInputIntoAFullyQualifiedPath(value)); } /** * If the user input starts with a tilde character (~), replace the tilde * character with the user's home directory. If the user input does not * start with a tilde, simply return the original user input without any * changes if the input specifies an absolute path, or return an absolute * path based on the working directory if the input specifies a relative * path. * * @param userInput the user input, which may commence with a tilde * (required) * @return a string that is guaranteed to no longer contain a tilde as the * first character (never null) */ private String convertUserInputIntoAFullyQualifiedPath(final String userInput) { if (denotesAbsolutePath(userInput)) { // Input is already in a fully-qualified path form return userInput; } if (userInput.startsWith(HOME_DIRECTORY_SYMBOL)) { // Replace this symbol with the user's actual home directory Validate.notNull(home, "Home directory could not be determined from system properties"); if (userInput.length() > 1) { return home + userInput.substring(1); } } // The path is working directory specific, so prepend the working // directory final String fullPath = getWorkingDirectoryAsString() + userInput; return fullPath; } /** * 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 */ private boolean denotesAbsolutePath(final String fileName) { if (SystemUtils.IS_OS_WINDOWS) { // first check for drive letter if (WINDOWS_DRIVE_PATH.matcher(fileName).matches()) { return true; } } return fileName.startsWith(File.separator); } public boolean getAllPossibleValues(final List<Completion> completions, final Class<?> requiredType, final String originalUserInput, final String optionContext, final MethodTarget target) { String adjustedUserInput = convertUserInputIntoAFullyQualifiedPath(originalUserInput); final String directoryData = adjustedUserInput.substring(0, adjustedUserInput.lastIndexOf(File.separator) + 1); adjustedUserInput = adjustedUserInput.substring(adjustedUserInput.lastIndexOf(File.separator) + 1); populate(completions, adjustedUserInput, originalUserInput, directoryData); return false; } /** * @return the "current working directory" this {@link FileConverter} should * use if the user fails to provide an explicit directory in their * input (required) */ protected abstract File getWorkingDirectory(); private String getWorkingDirectoryAsString() { try { return getWorkingDirectory().getCanonicalPath() + File.separator; } catch (final Exception e) { throw new IllegalStateException(e); } } protected void populate(final List<Completion> completions, final String adjustedUserInput, final String originalUserInput, final String directoryData) { final File directory = new File(directoryData); if (!directory.isDirectory()) { return; } for (final File file : directory.listFiles()) { if (adjustedUserInput == null || adjustedUserInput.length() == 0 || file.getName().toLowerCase().startsWith(adjustedUserInput.toLowerCase())) { String completion = ""; if (directoryData.length() > 0) { completion += directoryData; } completion += file.getName(); completion = convertCompletionBackIntoUserInputStyle(originalUserInput, completion); if (file.isDirectory()) { completions.add(new Completion(completion + File.separator)); } else { completions.add(new Completion(completion)); } } } } public boolean supports(final Class<?> requiredType, final String optionContext) { return File.class.isAssignableFrom(requiredType); } }