177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair/*
277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * Copyright 2008 the original author or authors.
377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair *
477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * Licensed under the Apache License, Version 2.0 (the "License");
577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * you may not use this file except in compliance with the License.
677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * You may obtain a copy of the License at
777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair *
877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair *      http://www.apache.org/licenses/LICENSE-2.0
977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair *
1077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * Unless required by applicable law or agreed to in writing, software
1177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * distributed under the License is distributed on an "AS IS" BASIS,
1277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * See the License for the specific language governing permissions and
1477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * limitations under the License.
1577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair */
1677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairpackage org.mockftpserver.fake.filesystem;
1777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
1877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport org.apache.log4j.Logger;
1977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport org.mockftpserver.core.util.Assert;
2077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport org.mockftpserver.core.util.PatternUtil;
2177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport org.mockftpserver.core.util.StringUtil;
2277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
2377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.util.ArrayList;
2477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.util.Collections;
2577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.util.Date;
2677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.util.HashMap;
2777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.util.Iterator;
2877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.util.List;
2977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairimport java.util.Map;
3077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
3177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair/**
3277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * Abstract superclass for implementation of the FileSystem interface that manage the files
3377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * and directories in memory, simulating a real file system.
3477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * <p/>
3577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * If the <code>createParentDirectoriesAutomatically</code> property is set to <code>true</code>,
3677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * then creating a directory or file will automatically create any parent directories (recursively)
3777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * that do not already exist. If <code>false</code>, then creating a directory or file throws an
3877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * exception if its parent directory does not exist. This value defaults to <code>true</code>.
3977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * <p/>
4077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * The <code>directoryListingFormatter</code> property holds an instance of            {@link DirectoryListingFormatter}                          ,
4177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * used by the <code>formatDirectoryListing</code> method to format directory listings in a
4277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * filesystem-specific manner. This property must be initialized by concrete subclasses.
4377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair *
4477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * @author Chris Mair
4577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair * @version $Revision$ - $Date$
4677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair */
4777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismairpublic abstract class AbstractFakeFileSystem implements FileSystem {
4877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
4977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private static final Logger LOG = Logger.getLogger(AbstractFakeFileSystem.class);
5077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
5177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
5277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * If <code>true</code>, creating a directory or file will automatically create
5377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * any parent directories (recursively) that do not already exist. If <code>false</code>,
5477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * then creating a directory or file throws an exception if its parent directory
5577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * does not exist. This value defaults to <code>true</code>.
5677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
5777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private boolean createParentDirectoriesAutomatically = true;
5877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
5977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
6077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * The {@link DirectoryListingFormatter} used by the {@link #formatDirectoryListing(FileSystemEntry)}
6177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * method. This must be initialized by concrete subclasses.
6277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
6377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private DirectoryListingFormatter directoryListingFormatter;
6477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
6577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private Map entries = new HashMap();
6677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
6777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    //-------------------------------------------------------------------------
6877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    // Public API
6977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    //-------------------------------------------------------------------------
7077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
7177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public boolean isCreateParentDirectoriesAutomatically() {
7277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return createParentDirectoriesAutomatically;
7377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
7477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
7577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public void setCreateParentDirectoriesAutomatically(boolean createParentDirectoriesAutomatically) {
7677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        this.createParentDirectoriesAutomatically = createParentDirectoriesAutomatically;
7777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
7877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
7977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public DirectoryListingFormatter getDirectoryListingFormatter() {
8077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return directoryListingFormatter;
8177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
8277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
8377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public void setDirectoryListingFormatter(DirectoryListingFormatter directoryListingFormatter) {
8477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        this.directoryListingFormatter = directoryListingFormatter;
8577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
8677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
8777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
8877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Add each of the entries in the specified List to this filesystem. Note that this does not affect
8977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * entries already existing within this filesystem.
9077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
9177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param entriesToAdd - the List of FileSystemEntry entries to add
9277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
9377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public void setEntries(List entriesToAdd) {
9477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        for (Iterator iter = entriesToAdd.iterator(); iter.hasNext();) {
9577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            FileSystemEntry entry = (FileSystemEntry) iter.next();
9677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            add(entry);
9777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
9877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
9977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
10077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
10177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Add the specified file system entry (file or directory) to this file system
10277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
10377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param entry - the FileSystemEntry to add
10477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
10577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public void add(FileSystemEntry entry) {
10677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        String path = entry.getPath();
10777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        checkForInvalidFilename(path);
10877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        if (getEntry(path) != null) {
10977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            throw new FileSystemException(path, "filesystem.pathAlreadyExists");
11077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
11177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
11277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        if (!parentDirectoryExists(path)) {
11377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            String parent = getParent(path);
11477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            if (createParentDirectoriesAutomatically) {
11577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                add(new DirectoryEntry(parent));
11677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            } else {
11777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                throw new FileSystemException(parent, "filesystem.parentDirectoryDoesNotExist");
11877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            }
11977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
12077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
12177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        // Set lastModified, if not already set
12277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        if (entry.getLastModified() == null) {
12377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            entry.setLastModified(new Date());
12477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
12577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
12677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        entries.put(getFileSystemEntryKey(path), entry);
12777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        entry.lockPath();
12877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
12977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
13077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
13177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Delete the file or directory specified by the path. Return true if the file is successfully
13277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * deleted, false otherwise. If the path refers to a directory, it must be empty. Return false
13377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * if the path does not refer to a valid file or directory or if it is a non-empty directory.
13477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
13577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path - the path of the file or directory to delete
13677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return true if the file or directory is successfully deleted
13777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @throws org.mockftpserver.core.util.AssertFailedException
13877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *          - if path is null
13977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see org.mockftpserver.fake.filesystem.FileSystem#delete(java.lang.String)
14077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
14177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public boolean delete(String path) {
14277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Assert.notNull(path, "path");
14377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
14477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        if (getEntry(path) != null && !hasChildren(path)) {
14577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            removeEntry(path);
14677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            return true;
14777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
14877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return false;
14977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
15077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
15177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
15277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Return true if there exists a file or directory at the specified path
15377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
15477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path - the path
15577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return true if the file/directory exists
15677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @throws AssertionError - if path is null
15777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see org.mockftpserver.fake.filesystem.FileSystem#exists(java.lang.String)
15877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
15977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public boolean exists(String path) {
16077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Assert.notNull(path, "path");
16177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return getEntry(path) != null;
16277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
16377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
16477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
16577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Return true if the specified path designates an existing directory, false otherwise
16677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
16777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path - the path
16877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return true if path is a directory, false otherwise
16977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @throws AssertionError - if path is null
17077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see org.mockftpserver.fake.filesystem.FileSystem#isDirectory(java.lang.String)
17177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
17277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public boolean isDirectory(String path) {
17377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Assert.notNull(path, "path");
17477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        FileSystemEntry entry = getEntry(path);
17577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return entry != null && entry.isDirectory();
17677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
17777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
17877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
17977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Return true if the specified path designates an existing file, false otherwise
18077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
18177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path - the path
18277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return true if path is a file, false otherwise
18377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @throws AssertionError - if path is null
18477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see org.mockftpserver.fake.filesystem.FileSystem#isFile(java.lang.String)
18577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
18677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public boolean isFile(String path) {
18777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Assert.notNull(path, "path");
18877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        FileSystemEntry entry = getEntry(path);
18977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return entry != null && !entry.isDirectory();
19077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
19177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
19277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
19377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Return the List of FileSystemEntry objects for the files in the specified directory or group of
19477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * files. If the path specifies a single file, then return a list with a single FileSystemEntry
19577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * object representing that file. If the path does not refer to an existing directory or
19677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * group of files, then an empty List is returned.
19777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
19877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path - the path specifying a directory or group of files; may contain wildcards (? or *)
19977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return the List of FileSystemEntry objects for the specified directory or file; may be empty
20077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see org.mockftpserver.fake.filesystem.FileSystem#listFiles(java.lang.String)
20177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
20277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public List listFiles(String path) {
20377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        if (isFile(path)) {
20477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            return Collections.singletonList(getEntry(path));
20577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
20677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
20777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        List entryList = new ArrayList();
20877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        List children = children(path);
20977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Iterator iter = children.iterator();
21077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        while (iter.hasNext()) {
21177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            String childPath = (String) iter.next();
21277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            FileSystemEntry fileSystemEntry = getEntry(childPath);
21377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            entryList.add(fileSystemEntry);
21477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
21577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return entryList;
21677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
21777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
21877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
21977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Return the List of filenames in the specified directory path or file path. If the path specifies
22077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * a single file, then return that single filename. The returned filenames do not
22177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * include a path. If the path does not refer to a valid directory or file path, then an empty List
22277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * is returned.
22377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
22477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path - the path specifying a directory or group of files; may contain wildcards (? or *)
22577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return the List of filenames (not including paths) for all files in the specified directory
22677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *         or file path; may be empty
22777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @throws AssertionError - if path is null
22877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see org.mockftpserver.fake.filesystem.FileSystem#listNames(java.lang.String)
22977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
23077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public List listNames(String path) {
23177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        if (isFile(path)) {
23277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            return Collections.singletonList(getName(path));
23377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
23477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
23577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        List filenames = new ArrayList();
23677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        List children = children(path);
23777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Iterator iter = children.iterator();
23877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        while (iter.hasNext()) {
23977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            String childPath = (String) iter.next();
24077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            FileSystemEntry fileSystemEntry = getEntry(childPath);
24177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            filenames.add(fileSystemEntry.getName());
24277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
24377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return filenames;
24477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
24577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
24677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
24777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Rename the file or directory. Specify the FROM path and the TO path. Throw an exception if the FROM path or
24877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * the parent directory of the TO path do not exist; or if the rename fails for another reason.
24977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
25077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param fromPath - the source (old) path + filename
25177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param toPath   - the target (new) path + filename
25277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @throws AssertionError      - if fromPath or toPath is null
25377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @throws FileSystemException - if the rename fails.
25477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
25577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public void rename(String fromPath, String toPath) {
25677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Assert.notNull(toPath, "toPath");
25777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Assert.notNull(fromPath, "fromPath");
25877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
25977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        FileSystemEntry entry = getRequiredEntry(fromPath);
26077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
26177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        String normalizedFromPath = normalize(fromPath);
26277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        String normalizedToPath = normalize(toPath);
26377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
26477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        if (!entry.isDirectory()) {
26577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            renamePath(entry, normalizedToPath);
26677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            return;
26777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
26877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
26977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        // Create the TO directory entry first so that the destination path exists when you
27077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        // move the children. Remove the FROM path after all children have been moved
27177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        add(new DirectoryEntry(normalizedToPath));
27277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
27377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        List children = descendents(fromPath);
27477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Iterator iter = children.iterator();
27577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        while (iter.hasNext()) {
27677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            String childPath = (String) iter.next();
27777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            FileSystemEntry child = getRequiredEntry(childPath);
27877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            String normalizedChildPath = normalize(child.getPath());
27977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            Assert.isTrue(normalizedChildPath.startsWith(normalizedFromPath), "Starts with FROM path");
28077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            String childToPath = normalizedToPath + normalizedChildPath.substring(normalizedFromPath.length());
28177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            renamePath(child, childToPath);
28277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
28377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Assert.isTrue(children(normalizedFromPath).isEmpty(), "Must have no children: " + normalizedFromPath);
28477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        removeEntry(normalizedFromPath);
28577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
28677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
28777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
28877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see java.lang.Object#toString()
28977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
29077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public String toString() {
29177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return this.getClass().getName() + entries;
29277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
29377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
29477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
29577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Return the formatted directory listing entry for the file represented by the specified FileSystemEntry
29677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
29777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param fileSystemEntry - the FileSystemEntry representing the file or directory entry to be formatted
29877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return the the formatted directory listing entry
29977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
30077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public String formatDirectoryListing(FileSystemEntry fileSystemEntry) {
30177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Assert.notNull(directoryListingFormatter, "directoryListingFormatter");
30277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Assert.notNull(fileSystemEntry, "fileSystemEntry");
30377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return directoryListingFormatter.format(fileSystemEntry);
30477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
30577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
30677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
30777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Build a path from the two path components. Concatenate path1 and path2. Insert the path
30877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * separator character in between if necessary (i.e., if both are non-empty and path1 does not already
30977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * end with a separator character AND path2 does not begin with one).
31077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
31177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path1 - the first path component may be null or empty
31277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path2 - the second path component may be null or empty
31377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return the normalized path resulting from concatenating path1 to path2
31477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
31577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public String path(String path1, String path2) {
31677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        StringBuffer buf = new StringBuffer();
31777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        if (path1 != null && path1.length() > 0) {
31877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            buf.append(path1);
31977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
32077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        if (path2 != null && path2.length() > 0) {
32177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            if ((path1 != null && path1.length() > 0)
32277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                    && (!isSeparator(path1.charAt(path1.length() - 1)))
32377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                    && (!isSeparator(path2.charAt(0)))) {
32477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                buf.append(this.getSeparator());
32577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            }
32677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            buf.append(path2);
32777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
32877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return normalize(buf.toString());
32977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
33077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
33177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
33277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Return the parent path of the specified path. If <code>path</code> specifies a filename,
33377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * then this method returns the path of the directory containing that file. If <code>path</code>
33477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * specifies a directory, the this method returns its parent directory. If <code>path</code> is
33577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * empty or does not have a parent component, then return an empty string.
33677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * <p/>
33777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * All path separators in the returned path are converted to the system-dependent separator character.
33877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
33977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path - the path
34077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return the parent of the specified path, or null if <code>path</code> has no parent
34177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @throws AssertionError - if path is null
34277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
34377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public String getParent(String path) {
34477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        List parts = normalizedComponents(path);
34577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        if (parts.size() < 2) {
34677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            return null;
34777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
34877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        parts.remove(parts.size() - 1);
34977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return componentsToPath(parts);
35077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
35177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
35277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
35377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Returns the name of the file or directory denoted by this abstract
35477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * pathname.  This is just the last name in the pathname's name
35577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * sequence.  If the pathname's name sequence is empty, then the empty string is returned.
35677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
35777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path - the path
35877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return The name of the file or directory denoted by this abstract pathname, or the
35977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *         empty string if this pathname's name sequence is empty
36077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
36177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public String getName(String path) {
36277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Assert.notNull(path, "path");
36377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        String normalized = normalize(path);
36477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        int separatorIndex = normalized.lastIndexOf(this.getSeparator());
36577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return (separatorIndex == -1) ? normalized : normalized.substring(separatorIndex + 1);
36677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
36777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
36877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
36977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Returns the FileSystemEntry object representing the file system entry at the specified path, or null
37077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * if the path does not specify an existing file or directory within this file system.
37177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
37277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path - the path of the file or directory within this file system
37377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return the FileSystemEntry containing the information for the file or directory, or else null
37477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @see FileSystem#getEntry(String)
37577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
37677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public FileSystemEntry getEntry(String path) {
37777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return (FileSystemEntry) entries.get(getFileSystemEntryKey(path));
37877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
37977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
38077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    //-------------------------------------------------------------------------
38177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    // Abstract Methods
38277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    //-------------------------------------------------------------------------
38377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
38477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
38577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path - the path
38677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return true if the specified dir/file path name is valid according to the current filesystem.
38777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
38877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    protected abstract boolean isValidName(String path);
38977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
39077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
39177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return the file system-specific file separator as a char
39277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
39377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    protected abstract char getSeparatorChar();
39477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
39577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
39677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param pathComponent - the component (piece) of the path to check
39777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return true if the specified path component is a root for this filesystem
39877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
39977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    protected abstract boolean isRoot(String pathComponent);
40077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
40177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
40277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Return true if the specified char is a separator character for this filesystem
40377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
40477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param c - the character to test
40577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return true if the specified char is a separator character
40677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
40777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    protected abstract boolean isSeparator(char c);
40877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
40977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    //-------------------------------------------------------------------------
41077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    // Internal Helper Methods
41177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    //-------------------------------------------------------------------------
41277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
41377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
41477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return the file system-specific file separator as a String
41577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
41677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    protected String getSeparator() {
41777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return Character.toString(getSeparatorChar());
41877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
41977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
42077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
42177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Return the normalized and unique key used to access the file system entry
42277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
42377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path - the path
42477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return the corresponding normalized key
42577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
42677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    protected String getFileSystemEntryKey(String path) {
42777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return normalize(path);
42877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
42977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
43077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
43177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Return the standard, normalized form of the path.
43277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
43377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path - the path
43477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return the path in a standard, unique, canonical form
43577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @throws AssertionError - if path is null
43677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
43777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    protected String normalize(String path) {
43877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return componentsToPath(normalizedComponents(path));
43977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
44077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
44177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
44277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Throw an InvalidFilenameException if the specified path is not valid.
44377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
44477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path - the path
44577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
44677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    protected void checkForInvalidFilename(String path) {
44777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        if (!isValidName(path)) {
44877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            throw new InvalidFilenameException(path);
44977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
45077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
45177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
45277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
45377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Rename the file system entry to the specified path name
45477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
45577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param entry  - the file system entry
45677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param toPath - the TO path (normalized)
45777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
45877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    protected void renamePath(FileSystemEntry entry, String toPath) {
45977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        String normalizedFrom = normalize(entry.getPath());
46077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        String normalizedTo = normalize(toPath);
46177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        LOG.info("renaming from [" + normalizedFrom + "] to [" + normalizedTo + "]");
46277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        FileSystemEntry newEntry = entry.cloneWithNewPath(normalizedTo);
46377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        add(newEntry);
46477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        // Do this at the end, in case the addEntry() failed
46577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        removeEntry(normalizedFrom);
46677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
46777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
46877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
46977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Return the FileSystemEntry for the specified path. Throw FileSystemException if the
47077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * specified path does not exist.
47177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
47277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path - the path
47377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return the FileSystemEntry
47477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @throws FileSystemException - if the specified path does not exist
47577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
47677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    protected FileSystemEntry getRequiredEntry(String path) {
47777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        FileSystemEntry entry = getEntry(path);
47877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        if (entry == null) {
47977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            LOG.error("Path does not exist: " + path);
48077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            throw new FileSystemException(normalize(path), "filesystem.doesNotExist");
48177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
48277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return entry;
48377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
48477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
48577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
48677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Return the components of the specified path as a List. The components are normalized, and
48777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * the returned List does not include path separator characters.
48877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
48977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path - the path
49077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return the List of normalized components
49177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
49277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    protected List normalizedComponents(String path) {
49377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Assert.notNull(path, "path");
49477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        char otherSeparator = this.getSeparatorChar() == '/' ? '\\' : '/';
49577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        String p = path.replace(otherSeparator, this.getSeparatorChar());
49677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
49777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        // TODO better way to do this
49877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        if (p.equals(this.getSeparator())) {
49977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            return Collections.singletonList("");
50077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
50177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        List result = new ArrayList();
50277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        if (p.length() > 0) {
50377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            String[] parts = p.split("\\" + this.getSeparator());
50477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            for (int i = 0; i < parts.length; i++) {
50577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                String part = parts[i];
50677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                if (part.equals("..")) {
50777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                    result.remove(result.size() - 1);
50877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                } else if (!part.equals(".")) {
50977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                    result.add(part);
51077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                }
51177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            }
51277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
51377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return result;
51477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
51577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
51677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
51777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Build a path from the specified list of path components
51877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
51977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param components - the list of path components
52077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return the resulting path
52177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
52277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    protected String componentsToPath(List components) {
52377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        if (components.size() == 1) {
52477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            String first = (String) components.get(0);
52577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            if (first.length() == 0 || isRoot(first)) {
52677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                return first + this.getSeparator();
52777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            }
52877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
52977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return StringUtil.join(components, this.getSeparator());
53077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
53177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
53277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
53377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Return true if the specified path designates an absolute file path.
53477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
53577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path - the path
53677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return true if path is absolute, false otherwise
53777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @throws AssertionError - if path is null
53877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
53977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    public boolean isAbsolute(String path) {
54077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return isValidName(path);
54177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
54277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
54377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
54477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Return true if the specified path exists
54577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
54677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path - the path
54777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return true if the path exists
54877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
54977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private boolean pathExists(String path) {
55077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return getEntry(path) != null;
55177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
55277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
55377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
55477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * If the specified path has a parent, then verify that the parent exists
55577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
55677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path - the path
55777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return true if the parent of the specified path exists
55877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
55977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private boolean parentDirectoryExists(String path) {
56077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        String parent = getParent(path);
56177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return parent == null || pathExists(parent);
56277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
56377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
56477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
56577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Return true if the specified path represents a directory that contains one or more files or subdirectories
56677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
56777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path - the path
56877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return true if the path has child entries
56977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
57077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private boolean hasChildren(String path) {
57177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        if (!isDirectory(path)) {
57277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            return false;
57377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
57477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        String key = getFileSystemEntryKey(path);
57577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Iterator iter = entries.keySet().iterator();
57677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        while (iter.hasNext()) {
57777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            String p = (String) iter.next();
57877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            if (p.startsWith(key) && !key.equals(p)) {
57977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                return true;
58077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            }
58177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
58277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return false;
58377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
58477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
58577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
58677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Return the List of files or subdirectory paths that are descendents of the specified path
58777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
58877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path - the path
58977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return the List of the paths for the files and subdirectories that are children, grandchildren, etc.
59077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
59177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private List descendents(String path) {
59277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        if (isDirectory(path)) {
59377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            String normalizedPath = getFileSystemEntryKey(path);
59477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            String separator = (normalizedPath.endsWith(getSeparator())) ? "" : getSeparator();
59577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            String normalizedDirPrefix = normalizedPath + separator;
59677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            List descendents = new ArrayList();
59777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            Iterator iter = entries.entrySet().iterator();
59877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            while (iter.hasNext()) {
59977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                Map.Entry mapEntry = (Map.Entry) iter.next();
60077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                String p = (String) mapEntry.getKey();
60177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                if (p.startsWith(normalizedDirPrefix) && !normalizedPath.equals(p)) {
60277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                    FileSystemEntry fileSystemEntry = (FileSystemEntry) mapEntry.getValue();
60377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                    descendents.add(fileSystemEntry.getPath());
60477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                }
60577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            }
60677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            return descendents;
60777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
60877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return Collections.EMPTY_LIST;
60977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
61077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
61177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    /**
61277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * Return the List of files or subdirectory paths that are children of the specified path
61377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     *
61477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @param path - the path
61577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     * @return the List of the paths for the files and subdirectories that are children
61677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair     */
61777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private List children(String path) {
61877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        String lastComponent = getName(path);
61977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        boolean containsWildcards = PatternUtil.containsWildcards(lastComponent);
62077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        String dir = containsWildcards ? getParent(path) : path;
62177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        String pattern = containsWildcards ? PatternUtil.convertStringWithWildcardsToRegex(getName(path)) : null;
62277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        LOG.debug("path=" + path + " lastComponent=" + lastComponent + " containsWildcards=" + containsWildcards + " dir=" + dir + " pattern=" + pattern);
62377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
62477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        List descendents = descendents(dir);
62577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        List children = new ArrayList();
62677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        String normalizedDir = normalize(dir);
62777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        Iterator iter = descendents.iterator();
62877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        while (iter.hasNext()) {
62977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            String descendentPath = (String) iter.next();
63077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
63177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            boolean patternEmpty = pattern == null || pattern.length() == 0;
63277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            if (normalizedDir.equals(getParent(descendentPath)) &&
63377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                    (patternEmpty || (getName(descendentPath).matches(pattern)))) {
63477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair                children.add(descendentPath);
63577b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair            }
63677b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        }
63777b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        return children;
63877b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
63977b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
64077b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    private void removeEntry(String path) {
64177b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair        entries.remove(getFileSystemEntryKey(path));
64277b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair    }
64377b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair
64477b8661f08d1379c0bdf2af93d8004fced9f1ab0chrismair}