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