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