1a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair/*
2a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * Copyright 2008 the original author or authors.
3a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair *
4a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * Licensed under the Apache License, Version 2.0 (the "License");
5a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * you may not use this file except in compliance with the License.
6a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * You may obtain a copy of the License at
7a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair *
8a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair *      http://www.apache.org/licenses/LICENSE-2.0
9a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair *
10a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * Unless required by applicable law or agreed to in writing, software
11a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * distributed under the License is distributed on an "AS IS" BASIS,
12a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * See the License for the specific language governing permissions and
14a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * limitations under the License.
15a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair */
16a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairpackage org.mockftpserver.fake.filesystem;
17a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
18a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport org.apache.log4j.Logger;
19a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport org.mockftpserver.core.util.Assert;
20a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport org.mockftpserver.core.util.PatternUtil;
21a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport org.mockftpserver.core.util.StringUtil;
22a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
23a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport java.util.ArrayList;
24a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport java.util.Collections;
25a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport java.util.Date;
26a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport java.util.HashMap;
27a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport java.util.Iterator;
28a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport java.util.List;
29a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairimport java.util.Map;
30a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
31a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair/**
32a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * Abstract superclass for implementation of the FileSystem interface that manage the files
33a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * and directories in memory, simulating a real file system.
34a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * <p/>
35a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * If the <code>createParentDirectoriesAutomatically</code> property is set to <code>true</code>,
36a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * then creating a directory or file will automatically create any parent directories (recursively)
37a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * that do not already exist. If <code>false</code>, then creating a directory or file throws an
38a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * exception if its parent directory does not exist. This value defaults to <code>true</code>.
39a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * <p/>
40a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * The <code>directoryListingFormatter</code> property holds an instance of            {@link DirectoryListingFormatter}                          ,
41a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * used by the <code>formatDirectoryListing</code> method to format directory listings in a
42a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * filesystem-specific manner. This property must be initialized by concrete subclasses.
43a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair *
44a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * @author Chris Mair
45a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair * @version $Revision$ - $Date$
46a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair */
47a0ad464efff5f5e2d2523a3522cce6823ce05858chrismairpublic abstract class AbstractFakeFileSystem implements FileSystem {
48a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
49a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    private static final Logger LOG = Logger.getLogger(AbstractFakeFileSystem.class);
50a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
51a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
52a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * If <code>true</code>, creating a directory or file will automatically create
53a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * any parent directories (recursively) that do not already exist. If <code>false</code>,
54a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * then creating a directory or file throws an exception if its parent directory
55a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * does not exist. This value defaults to <code>true</code>.
56a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
57a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    private boolean createParentDirectoriesAutomatically = true;
58a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
59a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
60a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * The {@link DirectoryListingFormatter} used by the {@link #formatDirectoryListing(FileSystemEntry)}
61a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * method. This must be initialized by concrete subclasses.
62a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
63a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    private DirectoryListingFormatter directoryListingFormatter;
64a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
65a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    private Map entries = new HashMap();
66a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
67a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    //-------------------------------------------------------------------------
68a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    // Public API
69a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    //-------------------------------------------------------------------------
70a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
71a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public boolean isCreateParentDirectoriesAutomatically() {
72a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return createParentDirectoriesAutomatically;
73a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
74a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
75a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public void setCreateParentDirectoriesAutomatically(boolean createParentDirectoriesAutomatically) {
76a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        this.createParentDirectoriesAutomatically = createParentDirectoriesAutomatically;
77a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
78a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
79a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public DirectoryListingFormatter getDirectoryListingFormatter() {
80a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return directoryListingFormatter;
81a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
82a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
83a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public void setDirectoryListingFormatter(DirectoryListingFormatter directoryListingFormatter) {
84a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        this.directoryListingFormatter = directoryListingFormatter;
85a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
86a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
87a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
88a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Add each of the entries in the specified List to this filesystem. Note that this does not affect
89a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * entries already existing within this filesystem.
90a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
91a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param entriesToAdd - the List of FileSystemEntry entries to add
92a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
93a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public void setEntries(List entriesToAdd) {
94a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        for (Iterator iter = entriesToAdd.iterator(); iter.hasNext();) {
95a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            FileSystemEntry entry = (FileSystemEntry) iter.next();
96a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            add(entry);
97a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
98a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
99a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
100a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
101a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Add the specified file system entry (file or directory) to this file system
102a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
103a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param entry - the FileSystemEntry to add
104a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
105a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public void add(FileSystemEntry entry) {
106a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        String path = entry.getPath();
107a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        checkForInvalidFilename(path);
108a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (getEntry(path) != null) {
109a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            throw new FileSystemException(path, "filesystem.pathAlreadyExists");
110a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
111a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
112a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (!parentDirectoryExists(path)) {
113a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            String parent = getParent(path);
114a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            if (createParentDirectoriesAutomatically) {
115a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair                add(new DirectoryEntry(parent));
116a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            } else {
117a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair                throw new FileSystemException(parent, "filesystem.parentDirectoryDoesNotExist");
118a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            }
119a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
120a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
121a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        // Set lastModified, if not already set
122a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (entry.getLastModified() == null) {
123a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            entry.setLastModified(new Date());
124a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
125a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
126a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        entries.put(getFileSystemEntryKey(path), entry);
127a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        entry.lockPath();
128a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
129a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
130a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
131a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Delete the file or directory specified by the path. Return true if the file is successfully
132a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * deleted, false otherwise. If the path refers to a directory, it must be empty. Return false
133a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * if the path does not refer to a valid file or directory or if it is a non-empty directory.
134a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
135a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path - the path of the file or directory to delete
136a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return true if the file or directory is successfully deleted
137a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @throws org.mockftpserver.core.util.AssertFailedException
138a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *          - if path is null
139a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @see org.mockftpserver.fake.filesystem.FileSystem#delete(java.lang.String)
140a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
141a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public boolean delete(String path) {
142a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        Assert.notNull(path, "path");
143a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
144a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (getEntry(path) != null && !hasChildren(path)) {
145a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            removeEntry(path);
146a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            return true;
147a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
148a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return false;
149a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
150a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
151a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
152a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Return true if there exists a file or directory at the specified path
153a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
154a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path - the path
155a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return true if the file/directory exists
156a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @throws AssertionError - if path is null
157a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @see org.mockftpserver.fake.filesystem.FileSystem#exists(java.lang.String)
158a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
159a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public boolean exists(String path) {
160a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        Assert.notNull(path, "path");
161a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return getEntry(path) != null;
162a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
163a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
164a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
165a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Return true if the specified path designates an existing directory, false otherwise
166a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
167a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path - the path
168a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return true if path is a directory, false otherwise
169a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @throws AssertionError - if path is null
170a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @see org.mockftpserver.fake.filesystem.FileSystem#isDirectory(java.lang.String)
171a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
172a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public boolean isDirectory(String path) {
173a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        Assert.notNull(path, "path");
174a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        FileSystemEntry entry = getEntry(path);
175a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return entry != null && entry.isDirectory();
176a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
177a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
178a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
179a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Return true if the specified path designates an existing file, false otherwise
180a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
181a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path - the path
182a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return true if path is a file, false otherwise
183a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @throws AssertionError - if path is null
184a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @see org.mockftpserver.fake.filesystem.FileSystem#isFile(java.lang.String)
185a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
186a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public boolean isFile(String path) {
187a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        Assert.notNull(path, "path");
188a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        FileSystemEntry entry = getEntry(path);
189a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return entry != null && !entry.isDirectory();
190a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
191a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
192a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
193a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Return the List of FileSystemEntry objects for the files in the specified directory or group of
194a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * files. If the path specifies a single file, then return a list with a single FileSystemEntry
195a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * object representing that file. If the path does not refer to an existing directory or
196a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * group of files, then an empty List is returned.
197a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
198a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path - the path specifying a directory or group of files; may contain wildcards (? or *)
199a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return the List of FileSystemEntry objects for the specified directory or file; may be empty
200a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @see org.mockftpserver.fake.filesystem.FileSystem#listFiles(java.lang.String)
201a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
202a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public List listFiles(String path) {
203a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (isFile(path)) {
204a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            return Collections.singletonList(getEntry(path));
205a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
206a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
207a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        List entryList = new ArrayList();
208a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        List children = children(path);
209a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        Iterator iter = children.iterator();
210a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        while (iter.hasNext()) {
211a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            String childPath = (String) iter.next();
212a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            FileSystemEntry fileSystemEntry = getEntry(childPath);
213a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            entryList.add(fileSystemEntry);
214a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
215a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return entryList;
216a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
217a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
218a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
219a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Return the List of filenames in the specified directory path or file path. If the path specifies
220a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * a single file, then return that single filename. The returned filenames do not
221a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * include a path. If the path does not refer to a valid directory or file path, then an empty List
222a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * is returned.
223a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
224a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path - the path specifying a directory or group of files; may contain wildcards (? or *)
225a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return the List of filenames (not including paths) for all files in the specified directory
226a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *         or file path; may be empty
227a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @throws AssertionError - if path is null
228a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @see org.mockftpserver.fake.filesystem.FileSystem#listNames(java.lang.String)
229a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
230a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public List listNames(String path) {
231a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (isFile(path)) {
232a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            return Collections.singletonList(getName(path));
233a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
234a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
235a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        List filenames = new ArrayList();
236a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        List children = children(path);
237a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        Iterator iter = children.iterator();
238a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        while (iter.hasNext()) {
239a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            String childPath = (String) iter.next();
240a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            FileSystemEntry fileSystemEntry = getEntry(childPath);
241a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            filenames.add(fileSystemEntry.getName());
242a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
243a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return filenames;
244a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
245a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
246a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
247a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Rename the file or directory. Specify the FROM path and the TO path. Throw an exception if the FROM path or
248a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * the parent directory of the TO path do not exist; or if the rename fails for another reason.
249a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
250a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param fromPath - the source (old) path + filename
251a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param toPath   - the target (new) path + filename
252a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @throws AssertionError      - if fromPath or toPath is null
253a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @throws FileSystemException - if the rename fails.
254a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
255a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public void rename(String fromPath, String toPath) {
256a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        Assert.notNull(toPath, "toPath");
257a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        Assert.notNull(fromPath, "fromPath");
258a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
259a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        FileSystemEntry entry = getRequiredEntry(fromPath);
260a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
261a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        String normalizedFromPath = normalize(fromPath);
262a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        String normalizedToPath = normalize(toPath);
263a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
264a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (!entry.isDirectory()) {
265a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            renamePath(entry, normalizedToPath);
266a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            return;
267a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
268a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
269a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        // Create the TO directory entry first so that the destination path exists when you
270a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        // move the children. Remove the FROM path after all children have been moved
271a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        add(new DirectoryEntry(normalizedToPath));
272a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
273a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        List children = descendents(fromPath);
274a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        Iterator iter = children.iterator();
275a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        while (iter.hasNext()) {
276a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            String childPath = (String) iter.next();
277a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            FileSystemEntry child = getRequiredEntry(childPath);
278a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            String normalizedChildPath = normalize(child.getPath());
279a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            Assert.isTrue(normalizedChildPath.startsWith(normalizedFromPath), "Starts with FROM path");
280a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            String childToPath = normalizedToPath + normalizedChildPath.substring(normalizedFromPath.length());
281a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            renamePath(child, childToPath);
282a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
283a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        Assert.isTrue(children(normalizedFromPath).isEmpty(), "Must have no children: " + normalizedFromPath);
284a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        removeEntry(normalizedFromPath);
285a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
286a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
287a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
288a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @see java.lang.Object#toString()
289a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
290a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public String toString() {
291a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return this.getClass().getName() + entries;
292a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
293a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
294a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
295a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Return the formatted directory listing entry for the file represented by the specified FileSystemEntry
296a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
297a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param fileSystemEntry - the FileSystemEntry representing the file or directory entry to be formatted
298a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return the the formatted directory listing entry
299a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
300a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public String formatDirectoryListing(FileSystemEntry fileSystemEntry) {
301a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        Assert.notNull(directoryListingFormatter, "directoryListingFormatter");
302a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        Assert.notNull(fileSystemEntry, "fileSystemEntry");
303a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return directoryListingFormatter.format(fileSystemEntry);
304a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
305a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
306a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
307a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Build a path from the two path components. Concatenate path1 and path2. Insert the path
308a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * separator character in between if necessary (i.e., if both are non-empty and path1 does not already
309a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * end with a separator character AND path2 does not begin with one).
310a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
311a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path1 - the first path component may be null or empty
312a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path2 - the second path component may be null or empty
313a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return the path resulting from concatenating path1 to path2
314a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
315a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public String path(String path1, String path2) {
316a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        StringBuffer buf = new StringBuffer();
317a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (path1 != null && path1.length() > 0) {
318a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            buf.append(path1);
319a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
320a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (path2 != null && path2.length() > 0) {
321a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            if ((path1 != null && path1.length() > 0)
322a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair                    && (!isSeparator(path1.charAt(path1.length() - 1)))
323a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair                    && (!isSeparator(path2.charAt(0)))) {
324a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair                buf.append(this.getSeparator());
325a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            }
326a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            buf.append(path2);
327a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
328a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return buf.toString();
329a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
330a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
331a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
332a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Return the parent path of the specified path. If <code>path</code> specifies a filename,
333a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * then this method returns the path of the directory containing that file. If <code>path</code>
334a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * specifies a directory, the this method returns its parent directory. If <code>path</code> is
335a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * empty or does not have a parent component, then return an empty string.
336a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * <p/>
337a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * All path separators in the returned path are converted to the system-dependent separator character.
338a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
339a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path - the path
340a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return the parent of the specified path, or null if <code>path</code> has no parent
341a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @throws AssertionError - if path is null
342a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
343a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public String getParent(String path) {
344a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        List parts = normalizedComponents(path);
345a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (parts.size() < 2) {
346a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            return null;
347a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
348a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        parts.remove(parts.size() - 1);
349a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return componentsToPath(parts);
350a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
351a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
352a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
353a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Returns the name of the file or directory denoted by this abstract
354a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * pathname.  This is just the last name in the pathname's name
355a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * sequence.  If the pathname's name sequence is empty, then the empty string is returned.
356a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
357a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path - the path
358a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return The name of the file or directory denoted by this abstract pathname, or the
359a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *         empty string if this pathname's name sequence is empty
360a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
361a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public String getName(String path) {
362a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        Assert.notNull(path, "path");
363a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        String normalized = normalize(path);
364a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        int separatorIndex = normalized.lastIndexOf(this.getSeparator());
365a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return (separatorIndex == -1) ? normalized : normalized.substring(separatorIndex + 1);
366a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
367a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
368a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
369a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Returns the FileSystemEntry object representing the file system entry at the specified path, or null
370a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * if the path does not specify an existing file or directory within this file system.
371a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
372a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path - the path of the file or directory within this file system
373a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return the FileSystemEntry containing the information for the file or directory, or else null
374a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @see FileSystem#getEntry(String)
375a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
376a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public FileSystemEntry getEntry(String path) {
377a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return (FileSystemEntry) entries.get(getFileSystemEntryKey(path));
378a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
379a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
380a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    //-------------------------------------------------------------------------
381a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    // Abstract Methods
382a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    //-------------------------------------------------------------------------
383a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
384a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
385a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path - the path
386a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return true if the specified dir/file path name is valid according to the current filesystem.
387a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
388a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected abstract boolean isValidName(String path);
389a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
390a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
391a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return the file system-specific file separator as a char
392a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
393a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected abstract char getSeparatorChar();
394a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
395a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
396a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param pathComponent - the component (piece) of the path to check
397a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return true if the specified path component is a root for this filesystem
398a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
399a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected abstract boolean isRoot(String pathComponent);
400a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
401a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
402a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Return true if the specified char is a separator character for this filesystem
403a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
404a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param c - the character to test
405a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return true if the specified char is a separator character
406a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
407a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected abstract boolean isSeparator(char c);
408a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
409a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    //-------------------------------------------------------------------------
410a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    // Internal Helper Methods
411a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    //-------------------------------------------------------------------------
412a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
413a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
414a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return the file system-specific file separator as a String
415a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
416a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected String getSeparator() {
417a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return Character.toString(getSeparatorChar());
418a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
419a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
420a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
421a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Return the normalized and unique key used to access the file system entry
422a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
423a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path - the path
424a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return the corresponding normalized key
425a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
426a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected String getFileSystemEntryKey(String path) {
427a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return normalize(path);
428a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
429a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
430a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
431a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Return the standard, normalized form of the path.
432a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
433a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path - the path
434a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return the path in a standard, unique, canonical form
435a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @throws AssertionError - if path is null
436a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
437a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected String normalize(String path) {
438a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return componentsToPath(normalizedComponents(path));
439a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
440a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
441a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
442a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Throw an InvalidFilenameException if the specified path is not valid.
443a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
444a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path - the path
445a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
446a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected void checkForInvalidFilename(String path) {
447a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (!isValidName(path)) {
448a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            throw new InvalidFilenameException(path);
449a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
450a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
451a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
452a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
453a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Rename the file system entry to the specified path name
454a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
455a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param entry  - the file system entry
456a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param toPath - the TO path (normalized)
457a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
458a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected void renamePath(FileSystemEntry entry, String toPath) {
459a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        String normalizedFrom = normalize(entry.getPath());
460a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        String normalizedTo = normalize(toPath);
461a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        LOG.info("renaming from [" + normalizedFrom + "] to [" + normalizedTo + "]");
462a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        FileSystemEntry newEntry = entry.cloneWithNewPath(normalizedTo);
463a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        add(newEntry);
464a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        // Do this at the end, in case the addEntry() failed
465a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        removeEntry(normalizedFrom);
466a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
467a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
468a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
469a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Return the FileSystemEntry for the specified path. Throw FileSystemException if the
470a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * specified path does not exist.
471a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
472a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path - the path
473a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return the FileSystemEntry
474a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @throws FileSystemException - if the specified path does not exist
475a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
476a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected FileSystemEntry getRequiredEntry(String path) {
477a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        FileSystemEntry entry = getEntry(path);
478a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (entry == null) {
479a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            LOG.error("Path does not exist: " + path);
480a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            throw new FileSystemException(normalize(path), "filesystem.pathDoesNotExist");
481a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
482a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return entry;
483a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
484a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
485a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
486a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Return the components of the specified path as a List. The components are normalized, and
487a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * the returned List does not include path separator characters.
488a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
489a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path - the path
490a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return the List of normalized components
491a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
492a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected List normalizedComponents(String path) {
493a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        Assert.notNull(path, "path");
494a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        char otherSeparator = this.getSeparatorChar() == '/' ? '\\' : '/';
495a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        String p = path.replace(otherSeparator, this.getSeparatorChar());
496a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
497a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        // TODO better way to do this
498a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (p.equals(this.getSeparator())) {
499a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            return Collections.singletonList("");
500a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
501a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
502a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        String[] parts = p.split("\\" + this.getSeparator());
503a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        List result = new ArrayList();
504a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        for (int i = 0; i < parts.length; i++) {
505a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            String part = parts[i];
506a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            if (part.equals("..")) {
507a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair                result.remove(result.size() - 1);
508a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            } else if (!part.equals(".")) {
509a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair                result.add(part);
510a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            }
511a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
512a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return result;
513a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
514a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
515a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
516a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Build a path from the specified list of path components
517a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
518a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param components - the list of path components
519a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return the resulting path
520a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
521a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    protected String componentsToPath(List components) {
522a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (components.size() == 1) {
523a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            String first = (String) components.get(0);
524a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            if (first.length() == 0 || isRoot(first)) {
525a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair                return first + this.getSeparator();
526a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            }
527a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
528a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return StringUtil.join(components, this.getSeparator());
529a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
530a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
531a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
532a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Return true if the specified path designates an absolute file path.
533a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
534a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path - the path
535a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return true if path is absolute, false otherwise
536a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @throws AssertionError - if path is null
537a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
538a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    public boolean isAbsolute(String path) {
539a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return isValidName(path);
540a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
541a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
542a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
543a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Return true if the specified path exists
544a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
545a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path - the path
546a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return true if the path exists
547a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
548a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    private boolean pathExists(String path) {
549a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return getEntry(path) != null;
550a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
551a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
552a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
553a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * If the specified path has a parent, then verify that the parent exists
554a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
555a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path - the path
556a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return true if the parent of the specified path exists
557a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
558a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    private boolean parentDirectoryExists(String path) {
559a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        String parent = getParent(path);
560a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return parent == null || pathExists(parent);
561a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
562a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
563a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
564a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Return true if the specified path represents a directory that contains one or more files or subdirectories
565a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
566a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path - the path
567a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return true if the path has child entries
568a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
569a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    private boolean hasChildren(String path) {
570a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (!isDirectory(path)) {
571a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            return false;
572a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
573a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        String key = getFileSystemEntryKey(path);
574a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        Iterator iter = entries.keySet().iterator();
575a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        while (iter.hasNext()) {
576a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            String p = (String) iter.next();
577a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            if (p.startsWith(key) && !key.equals(p)) {
578a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair                return true;
579a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            }
580a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
581a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return false;
582a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
583a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
584a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
585a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Return the List of files or subdirectory paths that are descendents of the specified path
586a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
587a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path - the path
588a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return the List of the paths for the files and subdirectories that are children, grandchildren, etc.
589a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
590a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    private List descendents(String path) {
591a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        if (isDirectory(path)) {
592a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            String normalizedPath = getFileSystemEntryKey(path);
593a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            String separator = (normalizedPath.endsWith(getSeparator())) ? "" : getSeparator();
594a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            String normalizedDirPrefix = normalizedPath + separator;
595a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            List descendents = new ArrayList();
596a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            Iterator iter = entries.keySet().iterator();
597a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            while (iter.hasNext()) {
598a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair                String p = (String) iter.next();
599a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair                if (p.startsWith(normalizedDirPrefix) && !normalizedPath.equals(p)) {
600a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair                    descendents.add(p);
601a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair                }
602a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            }
603a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            return descendents;
604a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
605a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return Collections.EMPTY_LIST;
606a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
607a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
608a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    /**
609a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * Return the List of files or subdirectory paths that are children of the specified path
610a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     *
611a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @param path - the path
612a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     * @return the List of the paths for the files and subdirectories that are children
613a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair     */
614a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    private List children(String path) {
615a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        String lastComponent = getName(path);
616a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        boolean containsWildcards = PatternUtil.containsWildcards(lastComponent);
617a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        String dir = containsWildcards ? getParent(path) : path;
618a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        String pattern = containsWildcards ? PatternUtil.convertStringWithWildcardsToRegex(getName(path)) : null;
619a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        LOG.debug("path=" + path + " lastComponent=" + lastComponent + " containsWildcards=" + containsWildcards + " dir=" + dir + " pattern=" + pattern);
620a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
621a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        List descendents = descendents(dir);
622a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        List children = new ArrayList();
623a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        String normalizedDir = normalize(dir);
624a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        Iterator iter = descendents.iterator();
625a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        while (iter.hasNext()) {
626a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            String descendentPath = (String) iter.next();
627a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
628a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            boolean patternEmpty = pattern == null || pattern.length() == 0;
629a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            if (normalizedDir.equals(getParent(descendentPath)) &&
630a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair                    (patternEmpty || (getName(descendentPath).matches(pattern)))) {
631a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair                children.add(descendentPath);
632a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair            }
633a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        }
634a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        return children;
635a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
636a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
637a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    private void removeEntry(String path) {
638a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair        entries.remove(getFileSystemEntryKey(path));
639a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair    }
640a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair
641a0ad464efff5f5e2d2523a3522cce6823ce05858chrismair}