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