All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mockftpserver.fake.filesystem.AbstractFakeFileSystem Maven / Gradle / Ivy

Go to download

The MockFtpServer project provides mock/dummy FTP server implementations for testing FTP client code. Two FTP Server implementations are provided, each at a different level of abstraction. FakeFtpServer provides a higher-level abstraction. You define a virtual file system, including directories and files, as well as a set of valid user accounts and credentials. The FakeFtpServer then responds with appropriate replies and reply codes based on that configuration. StubFtpServer, on the other hand, is a lower-level "stub" implementation. You configure the individual FTP server commands to return custom data or reply codes, allowing simulation of either success or failure scenarios. You can also verify expected command invocations.

There is a newer version: 3.2.0
Show newest version
/*
 * Copyright 2008 the original author or authors.
 *
 * Licensed 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 org.mockftpserver.fake.filesystem;

import org.mockftpserver.core.util.Assert;
import org.mockftpserver.core.util.PatternUtil;
import org.mockftpserver.core.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Abstract superclass for implementation of the FileSystem interface that manage the files
 * and directories in memory, simulating a real file system.
 *
 * 

If the createParentDirectoriesAutomatically property is set to true, * then creating a directory or file will automatically create any parent directories (recursively) * that do not already exist. If false, then creating a directory or file throws an * exception if its parent directory does not exist. This value defaults to true. * *

The directoryListingFormatter property holds an instance of {@link DirectoryListingFormatter} , * used by the formatDirectoryListing method to format directory listings in a * filesystem-specific manner. This property must be initialized by concrete subclasses. * *

The systemName property holds the default value returned by this FileSystem for the FTP SYST command. * * @author Chris Mair */ public abstract class AbstractFakeFileSystem implements FileSystem { private static final Logger LOG = LoggerFactory.getLogger(AbstractFakeFileSystem.class); /** * If true, creating a directory or file will automatically create * any parent directories (recursively) that do not already exist. If false, * then creating a directory or file throws an exception if its parent directory * does not exist. This value defaults to true. */ private boolean createParentDirectoriesAutomatically = true; private String systemName; /** * The {@link DirectoryListingFormatter} used by the {@link #formatDirectoryListing(FileSystemEntry)} * method. This must be initialized by concrete subclasses. */ private DirectoryListingFormatter directoryListingFormatter; private Map entries = new ConcurrentHashMap(); //------------------------------------------------------------------------- // Public API //------------------------------------------------------------------------- public boolean isCreateParentDirectoriesAutomatically() { return createParentDirectoriesAutomatically; } public void setCreateParentDirectoriesAutomatically(boolean createParentDirectoriesAutomatically) { this.createParentDirectoriesAutomatically = createParentDirectoriesAutomatically; } public DirectoryListingFormatter getDirectoryListingFormatter() { return directoryListingFormatter; } public void setDirectoryListingFormatter(DirectoryListingFormatter directoryListingFormatter) { this.directoryListingFormatter = directoryListingFormatter; } /** * Add each of the entries in the specified List to this filesystem. Note that this does not affect * entries already existing within this filesystem. * * @param entriesToAdd - the List of FileSystemEntry entries to add */ public void setEntries(List entriesToAdd) { for (Iterator iter = entriesToAdd.iterator(); iter.hasNext();) { FileSystemEntry entry = (FileSystemEntry) iter.next(); add(entry); } } /** * Add the specified file system entry (file or directory) to this file system * * @param entry - the FileSystemEntry to add */ @Override public void add(FileSystemEntry entry) { String path = entry.getPath(); checkForInvalidFilename(path); if (getEntry(path) != null) { throw new FileSystemException(path, "filesystem.pathAlreadyExists"); } if (!parentDirectoryExists(path)) { String parent = getParent(path); if (createParentDirectoriesAutomatically) { add(new DirectoryEntry(parent)); } else { throw new FileSystemException(parent, "filesystem.parentDirectoryDoesNotExist"); } } // Set lastModified, if not already set if (entry.getLastModified() == null) { entry.setLastModified(new Date()); } entries.put(getFileSystemEntryKey(path), entry); entry.lockPath(); } /** * Delete the file or directory specified by the path. Return true if the file is successfully * deleted, false otherwise. If the path refers to a directory, it must be empty. Return false * if the path does not refer to a valid file or directory or if it is a non-empty directory. * * @param path - the path of the file or directory to delete * @return true if the file or directory is successfully deleted * @throws org.mockftpserver.core.util.AssertFailedException * - if path is null * @see org.mockftpserver.fake.filesystem.FileSystem#delete(java.lang.String) */ @Override public boolean delete(String path) { Assert.notNull(path, "path"); if (getEntry(path) != null && !hasChildren(path)) { removeEntry(path); return true; } return false; } /** * Return true if there exists a file or directory at the specified path * * @param path - the path * @return true if the file/directory exists * @throws AssertionError - if path is null * @see org.mockftpserver.fake.filesystem.FileSystem#exists(java.lang.String) */ @Override public boolean exists(String path) { Assert.notNull(path, "path"); return getEntry(path) != null; } /** * Return true if the specified path designates an existing directory, false otherwise * * @param path - the path * @return true if path is a directory, false otherwise * @throws AssertionError - if path is null * @see org.mockftpserver.fake.filesystem.FileSystem#isDirectory(java.lang.String) */ @Override public boolean isDirectory(String path) { Assert.notNull(path, "path"); FileSystemEntry entry = getEntry(path); return entry != null && entry.isDirectory(); } /** * Return true if the specified path designates an existing file, false otherwise * * @param path - the path * @return true if path is a file, false otherwise * @throws AssertionError - if path is null * @see org.mockftpserver.fake.filesystem.FileSystem#isFile(java.lang.String) */ @Override public boolean isFile(String path) { Assert.notNull(path, "path"); FileSystemEntry entry = getEntry(path); return entry != null && !entry.isDirectory(); } /** * Return the List of FileSystemEntry objects for the files in the specified directory or group of * files. If the path specifies a single file, then return a list with a single FileSystemEntry * object representing that file. If the path does not refer to an existing directory or * group of files, then an empty List is returned. * * @param path - the path specifying a directory or group of files; may contain wildcards (? or *) * @return the List of FileSystemEntry objects for the specified directory or file; may be empty * @see org.mockftpserver.fake.filesystem.FileSystem#listFiles(java.lang.String) */ @Override public List listFiles(String path) { if (isFile(path)) { return Collections.singletonList(getEntry(path)); } List entryList = new ArrayList(); List children = children(path); Iterator iter = children.iterator(); while (iter.hasNext()) { String childPath = (String) iter.next(); FileSystemEntry fileSystemEntry = getEntry(childPath); entryList.add(fileSystemEntry); } return entryList; } /** * Return the List of filenames in the specified directory path or file path. If the path specifies * a single file, then return that single filename. The returned filenames do not * include a path. If the path does not refer to a valid directory or file path, then an empty List * is returned. * * @param path - the path specifying a directory or group of files; may contain wildcards (? or *) * @return the List of filenames (not including paths) for all files in the specified directory * or file path; may be empty * @throws AssertionError - if path is null * @see org.mockftpserver.fake.filesystem.FileSystem#listNames(java.lang.String) */ @Override public List listNames(String path) { if (isFile(path)) { return Collections.singletonList(getName(path)); } List filenames = new ArrayList(); List children = children(path); Iterator iter = children.iterator(); while (iter.hasNext()) { String childPath = (String) iter.next(); FileSystemEntry fileSystemEntry = getEntry(childPath); filenames.add(fileSystemEntry.getName()); } return filenames; } /** * Rename the file or directory. Specify the FROM path and the TO path. Throw an exception if the FROM path or * the parent directory of the TO path do not exist; or if the rename fails for another reason. * * @param fromPath - the source (old) path + filename * @param toPath - the target (new) path + filename * @throws AssertionError - if fromPath or toPath is null * @throws FileSystemException - if the rename fails. */ @Override public void rename(String fromPath, String toPath) { Assert.notNull(toPath, "toPath"); Assert.notNull(fromPath, "fromPath"); FileSystemEntry entry = getRequiredEntry(fromPath); if (exists(toPath)) { throw new FileSystemException(toPath, "filesystem.alreadyExists"); } String normalizedFromPath = normalize(fromPath); String normalizedToPath = normalize(toPath); if (!entry.isDirectory()) { renamePath(entry, normalizedToPath); return; } if (normalizedToPath.startsWith(normalizedFromPath + this.getSeparator())) { throw new FileSystemException(toPath, "filesystem.renameFailed"); } // Create the TO directory entry first so that the destination path exists when you // move the children. Remove the FROM path after all children have been moved add(new DirectoryEntry(normalizedToPath)); List children = descendants(fromPath); Collections.sort(children); // ensure that parents are listed before children Iterator iter = children.iterator(); while (iter.hasNext()) { String childPath = (String) iter.next(); FileSystemEntry child = getRequiredEntry(childPath); String normalizedChildPath = normalize(child.getPath()); Assert.isTrue(normalizedChildPath.startsWith(normalizedFromPath), "Starts with FROM path"); String childToPath = normalizedToPath + normalizedChildPath.substring(normalizedFromPath.length()); renamePath(child, childToPath); } Assert.isTrue(children(normalizedFromPath).isEmpty(), "Must have no children: " + normalizedFromPath); removeEntry(normalizedFromPath); } @Override public String toString() { return this.getClass().getName() + entries; } /** * Return the formatted directory listing entry for the file represented by the specified FileSystemEntry * * @param fileSystemEntry - the FileSystemEntry representing the file or directory entry to be formatted * @return the the formatted directory listing entry */ @Override public String formatDirectoryListing(FileSystemEntry fileSystemEntry) { Assert.notNull(directoryListingFormatter, "directoryListingFormatter"); Assert.notNull(fileSystemEntry, "fileSystemEntry"); return directoryListingFormatter.format(fileSystemEntry); } /** * Build a path from the two path components. Concatenate path1 and path2. Insert the path * separator character in between if necessary (i.e., if both are non-empty and path1 does not already * end with a separator character AND path2 does not begin with one). * * @param path1 - the first path component may be null or empty * @param path2 - the second path component may be null or empty * @return the normalized path resulting from concatenating path1 to path2 */ @Override public String path(String path1, String path2) { StringBuffer buf = new StringBuffer(); if (path1 != null && path1.length() > 0) { buf.append(path1); } if (path2 != null && path2.length() > 0) { if ((path1 != null && path1.length() > 0) && (!isSeparator(path1.charAt(path1.length() - 1))) && (!isSeparator(path2.charAt(0)))) { buf.append(this.getSeparator()); } buf.append(path2); } return normalize(buf.toString()); } /** * Return the parent path of the specified path. If path specifies a filename, * then this method returns the path of the directory containing that file. If path * specifies a directory, the this method returns its parent directory. If path is * empty or does not have a parent component, then return an empty string. * *

All path separators in the returned path are converted to the system-dependent separator character. * * @param path - the path * @return the parent of the specified path, or null if path has no parent * @throws AssertionError - if path is null */ @Override public String getParent(String path) { List parts = normalizedComponents(path); if (parts.size() < 2) { return null; } parts.remove(parts.size() - 1); return componentsToPath(parts); } /** * Returns the name of the file or directory denoted by this abstract * pathname. This is just the last name in the pathname's name * sequence. If the pathname's name sequence is empty, then the empty string is returned. * * @param path - the path * @return The name of the file or directory denoted by this abstract pathname, or the * empty string if this pathname's name sequence is empty */ public String getName(String path) { Assert.notNull(path, "path"); String normalized = normalize(path); int separatorIndex = normalized.lastIndexOf(this.getSeparator()); return (separatorIndex == -1) ? normalized : normalized.substring(separatorIndex + 1); } /** * Returns the FileSystemEntry object representing the file system entry at the specified path, or null * if the path does not specify an existing file or directory within this file system. * * @param path - the path of the file or directory within this file system * @return the FileSystemEntry containing the information for the file or directory, or else null * @see FileSystem#getEntry(String) */ @Override public FileSystemEntry getEntry(String path) { return (FileSystemEntry) entries.get(getFileSystemEntryKey(path)); } @Override public String getSystemName() { return systemName; } public void setSystemName(String systemName) { this.systemName = systemName; } //------------------------------------------------------------------------- // Abstract Methods //------------------------------------------------------------------------- /** * @param path - the path * @return true if the specified dir/file path name is valid according to the current filesystem. */ protected abstract boolean isValidName(String path); /** * @return the file system-specific file separator as a char */ protected abstract char getSeparatorChar(); /** * @param pathComponent - the component (piece) of the path to check * @return true if the specified path component is a root for this filesystem */ protected abstract boolean isRoot(String pathComponent); /** * Return true if the specified char is a separator character for this filesystem * * @param c - the character to test * @return true if the specified char is a separator character */ protected abstract boolean isSeparator(char c); //------------------------------------------------------------------------- // Internal Helper Methods //------------------------------------------------------------------------- /** * @return the file system-specific file separator as a String */ protected String getSeparator() { return Character.toString(getSeparatorChar()); } /** * Return the normalized and unique key used to access the file system entry * * @param path - the path * @return the corresponding normalized key */ protected String getFileSystemEntryKey(String path) { return normalize(path); } /** * Return the standard, normalized form of the path. * * @param path - the path * @return the path in a standard, unique, canonical form * @throws AssertionError - if path is null */ protected String normalize(String path) { return componentsToPath(normalizedComponents(path)); } /** * Throw an InvalidFilenameException if the specified path is not valid. * * @param path - the path */ protected void checkForInvalidFilename(String path) { if (!isValidName(path)) { throw new InvalidFilenameException(path); } } /** * Rename the file system entry to the specified path name * * @param entry - the file system entry * @param toPath - the TO path (normalized) */ protected void renamePath(FileSystemEntry entry, String toPath) { String normalizedFrom = normalize(entry.getPath()); String normalizedTo = normalize(toPath); LOG.info("renaming from [" + normalizedFrom + "] to [" + normalizedTo + "]"); FileSystemEntry newEntry = entry.cloneWithNewPath(normalizedTo); add(newEntry); // Do this at the end, in case the addEntry() failed removeEntry(normalizedFrom); } /** * Return the FileSystemEntry for the specified path. Throw FileSystemException if the * specified path does not exist. * * @param path - the path * @return the FileSystemEntry * @throws FileSystemException - if the specified path does not exist */ protected FileSystemEntry getRequiredEntry(String path) { FileSystemEntry entry = getEntry(path); if (entry == null) { LOG.error("Path does not exist: " + path); throw new FileSystemException(normalize(path), "filesystem.doesNotExist"); } return entry; } /** * Return the components of the specified path as a List. The components are normalized, and * the returned List does not include path separator characters. * * @param path - the path * @return the List of normalized components */ protected List normalizedComponents(String path) { Assert.notNull(path, "path"); char otherSeparator = this.getSeparatorChar() == '/' ? '\\' : '/'; String p = path.replace(otherSeparator, this.getSeparatorChar()); // TODO better way to do this if (p.equals(this.getSeparator())) { return Collections.singletonList(""); } List result = new ArrayList(); if (p.length() > 0) { String[] parts = p.split("\\" + this.getSeparator()); for (int i = 0; i < parts.length; i++) { String part = parts[i]; if (part.equals("..")) { result.remove(result.size() - 1); } else if (!part.equals(".")) { result.add(part); } } } return result; } /** * Build a path from the specified list of path components * * @param components - the list of path components * @return the resulting path */ protected String componentsToPath(List components) { if (components.size() == 1) { String first = (String) components.get(0); if (first.length() == 0 || isRoot(first)) { return first + this.getSeparator(); } } return StringUtil.join(components, this.getSeparator()); } /** * Return true if the specified path designates an absolute file path. * * @param path - the path * @return true if path is absolute, false otherwise * @throws AssertionError - if path is null */ public boolean isAbsolute(String path) { return isValidName(path); } /** * Return true if the specified path exists * * @param path - the path * @return true if the path exists */ private boolean pathExists(String path) { return getEntry(path) != null; } /** * If the specified path has a parent, then verify that the parent exists * * @param path - the path * @return true if the parent of the specified path exists */ private boolean parentDirectoryExists(String path) { String parent = getParent(path); return parent == null || pathExists(parent); } /** * Return true if the specified path represents a directory that contains one or more files or subdirectories * * @param path - the path * @return true if the path has child entries */ private boolean hasChildren(String path) { if (!isDirectory(path)) { return false; } String key = getFileSystemEntryKey(path); Iterator iter = entries.keySet().iterator(); while (iter.hasNext()) { String p = (String) iter.next(); if (p.startsWith(key) && !key.equals(p)) { return true; } } return false; } /** * Return the List of files or subdirectory paths that are descendants of the specified path * * @param path - the path * @return the List of the paths for the files and subdirectories that are children, grandchildren, etc. */ private List descendants(String path) { if (isDirectory(path)) { String normalizedPath = getFileSystemEntryKey(path); String separator = (normalizedPath.endsWith(getSeparator())) ? "" : getSeparator(); String normalizedDirPrefix = normalizedPath + separator; List descendants = new ArrayList(); Iterator iter = entries.entrySet().iterator(); while (iter.hasNext()) { Map.Entry mapEntry = (Map.Entry) iter.next(); String p = (String) mapEntry.getKey(); if (p.startsWith(normalizedDirPrefix) && !normalizedPath.equals(p)) { FileSystemEntry fileSystemEntry = (FileSystemEntry) mapEntry.getValue(); descendants.add(fileSystemEntry.getPath()); } } return descendants; } return Collections.EMPTY_LIST; } /** * Return the List of files or subdirectory paths that are children of the specified path * * @param path - the path * @return the List of the paths for the files and subdirectories that are children */ private List children(String path) { String lastComponent = getName(path); boolean containsWildcards = PatternUtil.containsWildcards(lastComponent); String dir = containsWildcards ? getParent(path) : path; String pattern = containsWildcards ? PatternUtil.convertStringWithWildcardsToRegex(getName(path)) : null; LOG.debug("path=" + path + " lastComponent=" + lastComponent + " containsWildcards=" + containsWildcards + " dir=" + dir + " pattern=" + pattern); List descendents = descendants(dir); List children = new ArrayList(); String normalizedDir = normalize(dir); Iterator iter = descendents.iterator(); while (iter.hasNext()) { String descendentPath = (String) iter.next(); boolean patternEmpty = pattern == null || pattern.length() == 0; if (normalizedDir.equals(getParent(descendentPath)) && (patternEmpty || (getName(descendentPath).matches(pattern)))) { children.add(descendentPath); } } return children; } private void removeEntry(String path) { entries.remove(getFileSystemEntryKey(path)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy