001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.vfs2.provider; 018 019import java.io.FileNotFoundException; 020import java.io.IOException; 021import java.io.InputStream; 022import java.io.OutputStream; 023import java.net.MalformedURLException; 024import java.net.URL; 025import java.security.AccessController; 026import java.security.PrivilegedActionException; 027import java.security.PrivilegedExceptionAction; 028import java.security.cert.Certificate; 029import java.util.ArrayList; 030import java.util.Arrays; 031import java.util.Collections; 032import java.util.Iterator; 033import java.util.List; 034import java.util.Map; 035 036import org.apache.commons.vfs2.Capability; 037import org.apache.commons.vfs2.FileContent; 038import org.apache.commons.vfs2.FileContentInfoFactory; 039import org.apache.commons.vfs2.FileName; 040import org.apache.commons.vfs2.FileNotFolderException; 041import org.apache.commons.vfs2.FileObject; 042import org.apache.commons.vfs2.FileSelector; 043import org.apache.commons.vfs2.FileSystem; 044import org.apache.commons.vfs2.FileSystemException; 045import org.apache.commons.vfs2.FileType; 046import org.apache.commons.vfs2.FileUtil; 047import org.apache.commons.vfs2.NameScope; 048import org.apache.commons.vfs2.RandomAccessContent; 049import org.apache.commons.vfs2.Selectors; 050import org.apache.commons.vfs2.operations.DefaultFileOperations; 051import org.apache.commons.vfs2.operations.FileOperations; 052import org.apache.commons.vfs2.util.FileObjectUtils; 053import org.apache.commons.vfs2.util.RandomAccessMode; 054 055/** 056 * A partial file object implementation. 057 * 058 * @param <AFS> An AbstractFileSystem subclass 059 */ 060public abstract class AbstractFileObject<AFS extends AbstractFileSystem> implements FileObject { 061 /* 062 * TODO - Chop this class up - move all the protected methods to several interfaces, so that structure and content 063 * can be separately overridden. 064 * 065 * TODO - Check caps in methods like getChildren(), etc, and give better error messages (eg 'this file type does not 066 * support listing children', vs 'this is not a folder') 067 */ 068 069 private static final FileName[] EMPTY_FILE_ARRAY = {}; 070 071 private static final int INITIAL_LIST_SIZE = 5; 072 073 private final AbstractFileName fileName; 074 private final AFS fs; 075 076 private FileContent content; 077 // Cached info 078 private boolean attached; 079 private FileType type; 080 081 private FileObject parent; 082 // Changed to hold only the name of the children and let the object 083 // go into the global files cache 084 // private FileObject[] children; 085 private FileName[] children; 086 087 private List<Object> objects; 088 089 /** 090 * FileServices instance. 091 */ 092 private FileOperations operations; 093 094 /** 095 * 096 * @param name the file name - muse be an instance of {@link AbstractFileName} 097 * @param fs the file system 098 * @throws ClassCastException if {@code name} is not an instance of {@link AbstractFileName} 099 */ 100 protected AbstractFileObject(final AbstractFileName name, final AFS fs) { 101 this.fileName = name; 102 this.fs = fs; 103 fs.fileObjectHanded(this); 104 } 105 106 /** 107 * Traverses a file. 108 */ 109 private static void traverse(final DefaultFileSelectorInfo fileInfo, final FileSelector selector, 110 final boolean depthwise, final List<FileObject> selected) throws Exception { 111 // Check the file itself 112 final FileObject file = fileInfo.getFile(); 113 final int index = selected.size(); 114 115 // If the file is a folder, traverse it 116 if (file.getType().hasChildren() && selector.traverseDescendents(fileInfo)) { 117 final int curDepth = fileInfo.getDepth(); 118 fileInfo.setDepth(curDepth + 1); 119 120 // Traverse the children 121 final FileObject[] children = file.getChildren(); 122 for (final FileObject child : children) { 123 fileInfo.setFile(child); 124 traverse(fileInfo, selector, depthwise, selected); 125 } 126 127 fileInfo.setFile(file); 128 fileInfo.setDepth(curDepth); 129 } 130 131 // Add the file if doing depthwise traversal 132 if (selector.includeFile(fileInfo)) { 133 if (depthwise) { 134 // Add this file after its descendants 135 selected.add(file); 136 } else { 137 // Add this file before its descendants 138 selected.add(index, file); 139 } 140 } 141 } 142 143 /** 144 * Attaches to the file. 145 * 146 * @throws FileSystemException if an error occurs. 147 */ 148 private void attach() throws FileSystemException { 149 synchronized (fs) { 150 if (attached) { 151 return; 152 } 153 154 try { 155 // Attach and determine the file type 156 doAttach(); 157 attached = true; 158 // now the type could already be injected by doAttach (e.g from parent to child) 159 160 /* 161 * VFS-210: determine the type when really asked fore if (type == null) { setFileType(doGetType()); } if 162 * (type == null) { setFileType(FileType.IMAGINARY); } 163 */ 164 } catch (final Exception exc) { 165 throw new FileSystemException("vfs.provider/get-type.error", exc, fileName); 166 } 167 168 // fs.fileAttached(this); 169 } 170 } 171 172 /** 173 * Queries the object if a simple rename to the filename of {@code newfile} is possible. 174 * 175 * @param newfile the new filename 176 * @return true if rename is possible 177 */ 178 @Override 179 public boolean canRenameTo(final FileObject newfile) { 180 return fs == newfile.getFileSystem(); 181 } 182 183 /** 184 * Notifies the file that its children have changed. 185 * 186 * @param childName The name of the child. 187 * @param newType The type of the child. 188 * @throws Exception if an error occurs. 189 */ 190 protected void childrenChanged(final FileName childName, final FileType newType) throws Exception { 191 // TODO - this may be called when not attached 192 193 if (children != null && childName != null && newType != null) { 194 // TODO - figure out if children[] can be replaced by list 195 final ArrayList<FileName> list = new ArrayList<>(Arrays.asList(children)); 196 if (newType.equals(FileType.IMAGINARY)) { 197 list.remove(childName); 198 } else { 199 list.add(childName); 200 } 201 children = new FileName[list.size()]; 202 list.toArray(children); 203 } 204 205 // removeChildrenCache(); 206 onChildrenChanged(childName, newType); 207 } 208 209 /** 210 * Closes this file, and its content. 211 * 212 * @throws FileSystemException if an error occurs. 213 */ 214 @Override 215 public void close() throws FileSystemException { 216 FileSystemException exc = null; 217 218 // Close the content 219 if (content != null) { 220 try { 221 content.close(); 222 content = null; 223 } catch (final FileSystemException e) { 224 exc = e; 225 } 226 } 227 228 // Detach from the file 229 try { 230 detach(); 231 } catch (final Exception e) { 232 exc = new FileSystemException("vfs.provider/close.error", fileName, e); 233 } 234 235 if (exc != null) { 236 throw exc; 237 } 238 } 239 240 /** 241 * Compares two FileObjects (ignores case). 242 * 243 * @param file the object to compare. 244 * @return a negative integer, zero, or a positive integer when this object is less than, equal to, or greater than 245 * the given object. 246 */ 247 @Override 248 public int compareTo(final FileObject file) { 249 if (file == null) { 250 return 1; 251 } 252 return this.toString().compareToIgnoreCase(file.toString()); 253 } 254 255 /** 256 * Copies another file to this file. 257 * 258 * @param file The FileObject to copy. 259 * @param selector The FileSelector. 260 * @throws FileSystemException if an error occurs. 261 */ 262 @Override 263 public void copyFrom(final FileObject file, final FileSelector selector) throws FileSystemException { 264 if (!file.exists()) { 265 throw new FileSystemException("vfs.provider/copy-missing-file.error", file); 266 } 267 268 // Locate the files to copy across 269 final ArrayList<FileObject> files = new ArrayList<>(); 270 file.findFiles(selector, false, files); 271 272 // Copy everything across 273 for (final FileObject srcFile : files) { 274 // Determine the destination file 275 final String relPath = file.getName().getRelativeName(srcFile.getName()); 276 final FileObject destFile = resolveFile(relPath, NameScope.DESCENDENT_OR_SELF); 277 278 // Clean up the destination file, if necessary 279 if (destFile.exists() && destFile.getType() != srcFile.getType()) { 280 // The destination file exists, and is not of the same type, 281 // so delete it 282 // TODO - add a pluggable policy for deleting and overwriting existing files 283 destFile.deleteAll(); 284 } 285 286 // Copy across 287 try { 288 if (srcFile.getType().hasContent()) { 289 FileUtil.copyContent(srcFile, destFile); 290 } else if (srcFile.getType().hasChildren()) { 291 destFile.createFolder(); 292 } 293 } catch (final IOException e) { 294 throw new FileSystemException("vfs.provider/copy-file.error", e, srcFile, destFile); 295 } 296 } 297 } 298 299 /** 300 * Creates this file, if it does not exist. 301 * 302 * @throws FileSystemException if an error occurs. 303 */ 304 @Override 305 public void createFile() throws FileSystemException { 306 synchronized (fs) { 307 try { 308 // VFS-210: We do not want to trunc any existing file, checking for its existence is 309 // still required 310 if (exists() && !isFile()) { 311 throw new FileSystemException("vfs.provider/create-file.error", fileName); 312 } 313 314 if (!exists()) { 315 getOutputStream().close(); 316 endOutput(); 317 } 318 } catch (final RuntimeException re) { 319 throw re; 320 } catch (final Exception e) { 321 throw new FileSystemException("vfs.provider/create-file.error", fileName, e); 322 } 323 } 324 } 325 326 /** 327 * Creates this folder, if it does not exist. Also creates any ancestor files which do not exist. 328 * 329 * @throws FileSystemException if an error occurs. 330 */ 331 @Override 332 public void createFolder() throws FileSystemException { 333 synchronized (fs) { 334 // VFS-210: we create a folder only if it does not already exist. So this check should be safe. 335 if (getType().hasChildren()) { 336 // Already exists as correct type 337 return; 338 } 339 if (getType() != FileType.IMAGINARY) { 340 throw new FileSystemException("vfs.provider/create-folder-mismatched-type.error", fileName); 341 } 342 /* 343 * VFS-210: checking for writeable is not always possible as the security constraint might be more complex 344 * if (!isWriteable()) { throw new FileSystemException("vfs.provider/create-folder-read-only.error", name); 345 * } 346 */ 347 348 // Traverse up the hierarchy and make sure everything is a folder 349 final FileObject parent = getParent(); 350 if (parent != null) { 351 parent.createFolder(); 352 } 353 354 try { 355 // Create the folder 356 doCreateFolder(); 357 358 // Update cached info 359 handleCreate(FileType.FOLDER); 360 } catch (final RuntimeException re) { 361 throw re; 362 } catch (final Exception exc) { 363 throw new FileSystemException("vfs.provider/create-folder.error", fileName, exc); 364 } 365 } 366 } 367 368 /** 369 * Deletes this file. 370 * <p> 371 * TODO - This will not fail if this is a non-empty folder. 372 * 373 * @return true if this object has been deleted 374 * @throws FileSystemException if an error occurs. 375 */ 376 @Override 377 public boolean delete() throws FileSystemException { 378 return delete(Selectors.SELECT_SELF) > 0; 379 } 380 381 /** 382 * Deletes this file, and all children matching the {@code selector}. 383 * 384 * @param selector The FileSelector. 385 * @return the number of deleted files. 386 * @throws FileSystemException if an error occurs. 387 */ 388 @Override 389 public int delete(final FileSelector selector) throws FileSystemException { 390 int nuofDeleted = 0; 391 392 /* 393 * VFS-210 if (getType() == FileType.IMAGINARY) { // File does not exist return nuofDeleted; } 394 */ 395 396 // Locate all the files to delete 397 final ArrayList<FileObject> files = new ArrayList<>(); 398 findFiles(selector, true, files); 399 400 // Delete 'em 401 final int count = files.size(); 402 for (int i = 0; i < count; i++) { 403 final AbstractFileObject file = FileObjectUtils.getAbstractFileObject(files.get(i)); 404 // file.attach(); 405 406 // VFS-210: It seems impossible to me that findFiles will return a list with hidden files/directories 407 // in it, else it would not be hidden. Checking for the file-type seems ok in this case 408 // If the file is a folder, make sure all its children have been deleted 409 if (file.getType().hasChildren() && file.getChildren().length != 0) { 410 // Skip - as the selector forced us not to delete all files 411 continue; 412 } 413 414 // Delete the file 415 if (file.deleteSelf()) { 416 nuofDeleted++; 417 } 418 } 419 420 return nuofDeleted; 421 } 422 423 /** 424 * Deletes this file and all children. Shorthand for {@code delete(Selectors.SELECT_ALL)} 425 * 426 * @return the number of deleted files. 427 * @throws FileSystemException if an error occurs. 428 * @see #delete(FileSelector) 429 * @see Selectors#SELECT_ALL 430 */ 431 @Override 432 public int deleteAll() throws FileSystemException { 433 return this.delete(Selectors.SELECT_ALL); 434 } 435 436 /** 437 * Deletes this file, once all its children have been deleted 438 * 439 * @return true if this file has been deleted 440 * @throws FileSystemException if an error occurs. 441 */ 442 private boolean deleteSelf() throws FileSystemException { 443 synchronized (fs) { 444 // Its possible to delete a read-only file if you have write-execute access to the directory 445 446 /* 447 * VFS-210 if (getType() == FileType.IMAGINARY) { // File does not exist return false; } 448 */ 449 450 try { 451 // Delete the file 452 doDelete(); 453 454 // Update cached info 455 handleDelete(); 456 } catch (final RuntimeException re) { 457 throw re; 458 } catch (final Exception exc) { 459 throw new FileSystemException("vfs.provider/delete.error", exc, fileName); 460 } 461 462 return true; 463 } 464 } 465 466 /** 467 * Detaches this file, invalidating all cached info. This will force a call to {@link #doAttach} next time this file 468 * is used. 469 * 470 * @throws Exception if an error occurs. 471 */ 472 private void detach() throws Exception { 473 synchronized (fs) { 474 if (attached) { 475 try { 476 doDetach(); 477 } finally { 478 attached = false; 479 setFileType(null); 480 parent = null; 481 482 // fs.fileDetached(this); 483 484 removeChildrenCache(); 485 // children = null; 486 } 487 } 488 } 489 } 490 491 /** 492 * Attaches this file object to its file resource. 493 * <p> 494 * This method is called before any of the doBlah() or onBlah() methods. Sub-classes can use this method to perform 495 * lazy initialisation. 496 * <p> 497 * This implementation does nothing. 498 * 499 * @throws Exception if an error occurs. 500 */ 501 protected void doAttach() throws Exception { 502 } 503 504 /** 505 * Create a FileContent implementation. 506 * 507 * @return The FileContent. 508 * @throws FileSystemException if an error occurs. 509 * @since 2.0 510 */ 511 protected FileContent doCreateFileContent() throws FileSystemException { 512 return new DefaultFileContent(this, getFileContentInfoFactory()); 513 } 514 515 /** 516 * Creates this file as a folder. Is only called when: 517 * <ul> 518 * <li>{@link #doGetType} returns {@link FileType#IMAGINARY}.</li> 519 * <li>The parent folder exists and is writeable, or this file is the root of the file system.</li> 520 * </ul> 521 * This implementation throws an exception. 522 * 523 * @throws Exception if an error occurs. 524 */ 525 protected void doCreateFolder() throws Exception { 526 throw new FileSystemException("vfs.provider/create-folder-not-supported.error"); 527 } 528 529 /** 530 * Deletes the file. Is only called when: 531 * <ul> 532 * <li>{@link #doGetType} does not return {@link FileType#IMAGINARY}.</li> 533 * <li>{@link #doIsWriteable} returns true.</li> 534 * <li>This file has no children, if a folder.</li> 535 * </ul> 536 * This implementation throws an exception. 537 * 538 * @throws Exception if an error occurs. 539 */ 540 protected void doDelete() throws Exception { 541 throw new FileSystemException("vfs.provider/delete-not-supported.error"); 542 } 543 544 /** 545 * Detaches this file object from its file resource. 546 * <p> 547 * Called when this file is closed. Note that the file object may be reused later, so should be able to be 548 * reattached. 549 * <p> 550 * This implementation does nothing. 551 * 552 * @throws Exception if an error occurs. 553 */ 554 protected void doDetach() throws Exception { 555 } 556 557 /** 558 * Returns the attributes of this file. Is only called if {@link #doGetType} does not return 559 * {@link FileType#IMAGINARY}. 560 * <p> 561 * This implementation always returns an empty map. 562 * 563 * @return The attributes of the file. 564 * @throws Exception if an error occurs. 565 */ 566 protected Map<String, Object> doGetAttributes() throws Exception { 567 return Collections.emptyMap(); 568 } 569 570 /** 571 * Returns the certificates used to sign this file. Is only called if {@link #doGetType} does not return 572 * {@link FileType#IMAGINARY}. 573 * <p> 574 * This implementation always returns null. 575 * 576 * @return The certificates used to sign the file. 577 * @throws Exception if an error occurs. 578 */ 579 protected Certificate[] doGetCertificates() throws Exception { 580 return null; 581 } 582 583 /** 584 * Returns the size of the file content (in bytes). Is only called if {@link #doGetType} returns 585 * {@link FileType#FILE}. 586 * 587 * @return The size of the file in bytes. 588 * @throws Exception if an error occurs. 589 */ 590 protected abstract long doGetContentSize() throws Exception; 591 592 /** 593 * Creates an input stream to read the file content from. Is only called if {@link #doGetType} returns 594 * {@link FileType#FILE}. 595 * <p> 596 * It is guaranteed that there are no open output streams for this file when this method is called. 597 * <p> 598 * The returned stream does not have to be buffered. 599 * 600 * @return An InputStream to read the file content. 601 * @throws Exception if an error occurs. 602 */ 603 protected abstract InputStream doGetInputStream() throws Exception; 604 605 /** 606 * Returns the last modified time of this file. Is only called if {@link #doGetType} does not return 607 * {@link FileType#IMAGINARY}. 608 * <p> 609 * This implementation throws an exception. 610 * 611 * @return The last modification time. 612 * @throws Exception if an error occurs. 613 */ 614 protected long doGetLastModifiedTime() throws Exception { 615 throw new FileSystemException("vfs.provider/get-last-modified-not-supported.error"); 616 } 617 618 /** 619 * Creates an output stream to write the file content to. Is only called if: 620 * <ul> 621 * <li>{@link #doIsWriteable} returns true. 622 * <li>{@link #doGetType} returns {@link FileType#FILE}, or {@link #doGetType} returns {@link FileType#IMAGINARY}, 623 * and the file's parent exists and is a folder. 624 * </ul> 625 * It is guaranteed that there are no open stream (input or output) for this file when this method is called. 626 * <p> 627 * The returned stream does not have to be buffered. 628 * <p> 629 * This implementation throws an exception. 630 * 631 * @param bAppend true if the file should be appended to, false if it should be overwritten. 632 * @return An OutputStream to write to the file. 633 * @throws Exception if an error occurs. 634 */ 635 protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception { 636 throw new FileSystemException("vfs.provider/write-not-supported.error"); 637 } 638 639 /** 640 * Creates access to the file for random i/o. Is only called if {@link #doGetType} returns {@link FileType#FILE}. 641 * <p> 642 * It is guaranteed that there are no open output streams for this file when this method is called. 643 * 644 * @param mode The mode to access the file. 645 * @return The RandomAccessContext. 646 * @throws Exception if an error occurs. 647 */ 648 protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception { 649 throw new FileSystemException("vfs.provider/random-access-not-supported.error"); 650 } 651 652 /** 653 * Determines the type of this file. Must not return null. The return value of this method is cached, so the 654 * implementation can be expensive. 655 * 656 * @return the type of the file. 657 * @throws Exception if an error occurs. 658 */ 659 protected abstract FileType doGetType() throws Exception; 660 661 /** 662 * Determines if this file is executable. Is only called if {@link #doGetType} does not return 663 * {@link FileType#IMAGINARY}. 664 * <p> 665 * This implementation always returns false. 666 * 667 * @return true if the file is executable, false otherwise. 668 * @throws Exception if an error occurs. 669 */ 670 protected boolean doIsExecutable() throws Exception { 671 return false; 672 } 673 674 /** 675 * Determines if this file is hidden. Is only called if {@link #doGetType} does not return 676 * {@link FileType#IMAGINARY}. 677 * <p> 678 * This implementation always returns false. 679 * 680 * @return true if the file is hidden, false otherwise. 681 * @throws Exception if an error occurs. 682 */ 683 protected boolean doIsHidden() throws Exception { 684 return false; 685 } 686 687 /** 688 * Determines if this file can be read. Is only called if {@link #doGetType} does not return 689 * {@link FileType#IMAGINARY}. 690 * <p> 691 * This implementation always returns true. 692 * 693 * @return true if the file is readable, false otherwise. 694 * @throws Exception if an error occurs. 695 */ 696 protected boolean doIsReadable() throws Exception { 697 return true; 698 } 699 700 /** 701 * Checks if this fileObject is the same file as {@code destFile} just with a different name. E.g. for case 702 * insensitive filesystems like windows. 703 * 704 * @param destFile The file to compare to. 705 * @return true if the FileObjects are the same. 706 * @throws FileSystemException if an error occurs. 707 */ 708 protected boolean doIsSameFile(final FileObject destFile) throws FileSystemException { 709 return false; 710 } 711 712 /** 713 * Determines if this file can be written to. Is only called if {@link #doGetType} does not return 714 * {@link FileType#IMAGINARY}. 715 * <p> 716 * This implementation always returns true. 717 * 718 * @return true if the file is writable. 719 * @throws Exception if an error occurs. 720 */ 721 protected boolean doIsWriteable() throws Exception { 722 return true; 723 } 724 725 /** 726 * Lists the children of this file. Is only called if {@link #doGetType} returns {@link FileType#FOLDER}. The return 727 * value of this method is cached, so the implementation can be expensive. 728 * 729 * @return a possible empty String array if the file is a directory or null or an exception if the file is not a 730 * directory or can't be read. 731 * @throws Exception if an error occurs. 732 */ 733 protected abstract String[] doListChildren() throws Exception; 734 735 /** 736 * Lists the children of this file. 737 * <p> 738 * Is only called if {@link #doGetType} returns {@link FileType#FOLDER}. 739 * <p> 740 * The return value of this method is cached, so the implementation can be expensive.<br> 741 * Other than {@code doListChildren} you could return FileObject's to e.g. reinitialize the type of the file. 742 * <p> 743 * (Introduced for Webdav: "permission denied on resource" during getType()) 744 * 745 * @return The children of this FileObject. 746 * @throws Exception if an error occurs. 747 */ 748 protected FileObject[] doListChildrenResolved() throws Exception { 749 return null; 750 } 751 752 /** 753 * Removes an attribute of this file. 754 * <p> 755 * Is only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. 756 * <p> 757 * This implementation throws an exception. 758 * 759 * @param attrName The name of the attribute to remove. 760 * @throws Exception if an error occurs. 761 * @since 2.0 762 */ 763 protected void doRemoveAttribute(final String attrName) throws Exception { 764 throw new FileSystemException("vfs.provider/remove-attribute-not-supported.error"); 765 } 766 767 /** 768 * Renames the file. 769 * <p> 770 * Is only called when: 771 * <ul> 772 * <li>{@link #doIsWriteable} returns true.</li> 773 * </ul> 774 * <p> 775 * This implementation throws an exception. 776 * 777 * @param newFile A FileObject with the new file name. 778 * @throws Exception if an error occurs. 779 */ 780 protected void doRename(final FileObject newFile) throws Exception { 781 throw new FileSystemException("vfs.provider/rename-not-supported.error"); 782 } 783 784 /** 785 * Sets an attribute of this file. 786 * <p> 787 * Is only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. 788 * <p> 789 * This implementation throws an exception. 790 * 791 * @param attrName The attribute name. 792 * @param value The value to be associated with the attribute name. 793 * @throws Exception if an error occurs. 794 */ 795 protected void doSetAttribute(final String attrName, final Object value) throws Exception { 796 throw new FileSystemException("vfs.provider/set-attribute-not-supported.error"); 797 } 798 799 /** 800 * Make the file executable. 801 * <p> 802 * Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. 803 * <p> 804 * This implementation returns false. 805 * 806 * @param executable True to allow access, false to disallow. 807 * @param ownerOnly If {@code true}, the permission applies only to the owner; otherwise, it applies to everybody. 808 * @return true if the operation succeeded. 809 * @throws Exception Any Exception thrown is wrapped in FileSystemException. 810 * @see #setExecutable(boolean, boolean) 811 * @since 2.1 812 */ 813 protected boolean doSetExecutable(final boolean executable, final boolean ownerOnly) throws Exception { 814 return false; 815 } 816 817 /** 818 * Sets the last modified time of this file. 819 * <p> 820 * Is only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. 821 * <p> 822 * This implementation throws an exception. 823 * 824 * @param modtime The last modification time. 825 * @return true if the time was set. 826 * @throws Exception Any Exception thrown is wrapped in FileSystemException. 827 */ 828 protected boolean doSetLastModifiedTime(final long modtime) throws Exception { 829 throw new FileSystemException("vfs.provider/set-last-modified-not-supported.error"); 830 } 831 832 /** 833 * Make the file or folder readable. 834 * <p> 835 * Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. 836 * <p> 837 * This implementation returns false. 838 * 839 * @param readable True to allow access, false to disallow 840 * @param ownerOnly If {@code true}, the permission applies only to the owner; otherwise, it applies to everybody. 841 * @return true if the operation succeeded 842 * @throws Exception Any Exception thrown is wrapped in FileSystemException. 843 * @see #setReadable(boolean, boolean) 844 * @since 2.1 845 */ 846 protected boolean doSetReadable(final boolean readable, final boolean ownerOnly) throws Exception { 847 return false; 848 } 849 850 /** 851 * Make the file or folder writeable. 852 * <p> 853 * Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. 854 * 855 * @param writable True to allow access, false to disallow 856 * @param ownerOnly If {@code true}, the permission applies only to the owner; otherwise, it applies to everybody. 857 * @return true if the operation succeeded 858 * @throws Exception Any Exception thrown is wrapped in FileSystemException. 859 * @see #setWritable(boolean, boolean) 860 * @since 2.1 861 */ 862 protected boolean doSetWritable(final boolean writable, final boolean ownerOnly) throws Exception { 863 return false; 864 } 865 866 /** 867 * Called when the output stream for this file is closed. 868 * 869 * @throws Exception if an error occurs. 870 */ 871 protected void endOutput() throws Exception { 872 if (getType() == FileType.IMAGINARY) { 873 // File was created 874 handleCreate(FileType.FILE); 875 } else { 876 // File has changed 877 onChange(); 878 } 879 } 880 881 /** 882 * Determines if the file exists. 883 * 884 * @return true if the file exists, false otherwise, 885 * @throws FileSystemException if an error occurs. 886 */ 887 @Override 888 public boolean exists() throws FileSystemException { 889 return getType() != FileType.IMAGINARY; 890 } 891 892 private FileName[] extractNames(final FileObject[] objects) { 893 if (objects == null) { 894 return null; 895 } 896 897 final FileName[] names = new FileName[objects.length]; 898 for (int iterObjects = 0; iterObjects < objects.length; iterObjects++) { 899 names[iterObjects] = objects[iterObjects].getName(); 900 } 901 902 return names; 903 } 904 905 @Override 906 protected void finalize() throws Throwable { 907 fs.fileObjectDestroyed(this); 908 909 super.finalize(); 910 } 911 912 /** 913 * Finds the set of matching descendants of this file, in depthwise order. 914 * 915 * @param selector The FileSelector. 916 * @return list of files or null if the base file (this object) do not exist 917 * @throws FileSystemException if an error occurs. 918 */ 919 @Override 920 public FileObject[] findFiles(final FileSelector selector) throws FileSystemException { 921 final List<FileObject> list = this.listFiles(selector); 922 return list == null ? null : list.toArray(new FileObject[list.size()]); 923 } 924 925 /** 926 * Traverses the descendants of this file, and builds a list of selected files. 927 * 928 * @param selector The FileSelector. 929 * @param depthwise if true files are added after their descendants, before otherwise. 930 * @param selected A List of the located FileObjects. 931 * @throws FileSystemException if an error occurs. 932 */ 933 @Override 934 public void findFiles(final FileSelector selector, final boolean depthwise, final List<FileObject> selected) 935 throws FileSystemException { 936 try { 937 if (exists()) { 938 // Traverse starting at this file 939 final DefaultFileSelectorInfo info = new DefaultFileSelectorInfo(); 940 info.setBaseFolder(this); 941 info.setDepth(0); 942 info.setFile(this); 943 traverse(info, selector, depthwise, selected); 944 } 945 } catch (final Exception e) { 946 throw new FileSystemException("vfs.provider/find-files.error", fileName, e); 947 } 948 } 949 950 /** 951 * Returns the file system this file belongs to. 952 * 953 * @return The FileSystem this file is associated with. 954 */ 955 protected AFS getAbstractFileSystem() { 956 return fs; 957 } 958 959 /** 960 * Returns a child of this file. 961 * 962 * @param name The name of the child to locate. 963 * @return The FileObject for the file or null if the child does not exist. 964 * @throws FileSystemException if an error occurs. 965 */ 966 @Override 967 public FileObject getChild(final String name) throws FileSystemException { 968 // TODO - use a hashtable when there are a large number of children 969 final FileObject[] children = getChildren(); 970 for (final FileObject element : children) { 971 final FileName child = element.getName(); 972 // TODO - use a comparator to compare names 973 if (child.getBaseName().equals(name)) { 974 return resolveFile(child); 975 } 976 } 977 return null; 978 } 979 980 /** 981 * Returns the children of the file. 982 * 983 * @return an array of FileObjects, one per child. 984 * @throws FileSystemException if an error occurs. 985 */ 986 @Override 987 public FileObject[] getChildren() throws FileSystemException { 988 synchronized (fs) { 989 // VFS-210 990 if (!fs.hasCapability(Capability.LIST_CHILDREN)) { 991 throw new FileNotFolderException(fileName); 992 } 993 994 /* 995 * VFS-210 if (!getType().hasChildren()) { throw new 996 * FileSystemException("vfs.provider/list-children-not-folder.error", name); } 997 */ 998 attach(); 999 1000 // Use cached info, if present 1001 if (children != null) { 1002 return resolveFiles(children); 1003 } 1004 1005 // allow the filesystem to return resolved children. e.g. prefill type for webdav 1006 FileObject[] childrenObjects; 1007 try { 1008 childrenObjects = doListChildrenResolved(); 1009 children = extractNames(childrenObjects); 1010 } catch (final FileSystemException exc) { 1011 // VFS-210 1012 throw exc; 1013 } catch (final Exception exc) { 1014 throw new FileSystemException("vfs.provider/list-children.error", exc, fileName); 1015 } 1016 1017 if (childrenObjects != null) { 1018 return childrenObjects; 1019 } 1020 1021 // List the children 1022 final String[] files; 1023 try { 1024 files = doListChildren(); 1025 } catch (final FileSystemException exc) { 1026 // VFS-210 1027 throw exc; 1028 } catch (final Exception exc) { 1029 throw new FileSystemException("vfs.provider/list-children.error", exc, fileName); 1030 } 1031 1032 if (files == null) { 1033 // VFS-210 1034 // honor the new doListChildren contract 1035 // return null; 1036 throw new FileNotFolderException(fileName); 1037 } else if (files.length == 0) { 1038 // No children 1039 children = EMPTY_FILE_ARRAY; 1040 } else { 1041 // Create file objects for the children 1042 final FileName[] cache = new FileName[files.length]; 1043 for (int i = 0; i < files.length; i++) { 1044 final String file = files[i]; 1045 cache[i] = fs.getFileSystemManager().resolveName(fileName, file, NameScope.CHILD); 1046 } 1047 // VFS-285: only assign the children filenames after all of them have been 1048 // resolved successfully to prevent an inconsistent internal state 1049 children = cache; 1050 } 1051 1052 return resolveFiles(children); 1053 } 1054 } 1055 1056 /** 1057 * Returns the file's content. 1058 * 1059 * @return the FileContent for this FileObject. 1060 * @throws FileSystemException if an error occurs. 1061 */ 1062 @Override 1063 public FileContent getContent() throws FileSystemException { 1064 synchronized (fs) { 1065 attach(); 1066 if (content == null) { 1067 content = doCreateFileContent(); 1068 } 1069 return content; 1070 } 1071 } 1072 1073 /** 1074 * Creates the FileContentInfo factory. 1075 * 1076 * @return The FileContentInfoFactory. 1077 */ 1078 protected FileContentInfoFactory getFileContentInfoFactory() { 1079 return fs.getFileSystemManager().getFileContentInfoFactory(); 1080 } 1081 1082 /** 1083 * @return FileOperations interface that provides access to the operations API. 1084 * @throws FileSystemException if an error occurs. 1085 */ 1086 @Override 1087 public FileOperations getFileOperations() throws FileSystemException { 1088 if (operations == null) { 1089 operations = new DefaultFileOperations(this); 1090 } 1091 1092 return operations; 1093 } 1094 1095 /** 1096 * Returns the file system this file belongs to. 1097 * 1098 * @return The FileSystem this file is associated with. 1099 */ 1100 @Override 1101 public FileSystem getFileSystem() { 1102 return fs; 1103 } 1104 1105 /** 1106 * Returns an input stream to use to read the content of the file. 1107 * 1108 * @return The InputStream to access this file's content. 1109 * @throws FileSystemException if an error occurs. 1110 */ 1111 public InputStream getInputStream() throws FileSystemException { 1112 /* 1113 * VFS-210 if (!getType().hasContent()) { throw new FileSystemException("vfs.provider/read-not-file.error", 1114 * name); } if (!isReadable()) { throw new FileSystemException("vfs.provider/read-not-readable.error", name); } 1115 */ 1116 1117 // Get the raw input stream 1118 try { 1119 return doGetInputStream(); 1120 } catch (final org.apache.commons.vfs2.FileNotFoundException exc) { 1121 throw new org.apache.commons.vfs2.FileNotFoundException(fileName, exc); 1122 } catch (final FileNotFoundException exc) { 1123 throw new org.apache.commons.vfs2.FileNotFoundException(fileName, exc); 1124 } catch (final FileSystemException exc) { 1125 throw exc; 1126 } catch (final Exception exc) { 1127 throw new FileSystemException("vfs.provider/read.error", fileName, exc); 1128 } 1129 } 1130 1131 /** 1132 * Returns the name of the file. 1133 * 1134 * @return The FileName, never {@code null}. 1135 */ 1136 @Override 1137 public FileName getName() { 1138 return fileName; 1139 } 1140 1141 /** 1142 * Returns the receiver as a URI String for public display, like, without a password. 1143 * 1144 * @return A URI String without a password, never {@code null}. 1145 */ 1146 @Override 1147 public String getPublicURIString() { 1148 return fileName.getFriendlyURI(); 1149 } 1150 1151 /** 1152 * Prepares this file for writing. Makes sure it is either a file, or its parent folder exists. Returns an output 1153 * stream to use to write the content of the file to. 1154 * 1155 * @return An OutputStream where the new contents of the file can be written. 1156 * @throws FileSystemException if an error occurs. 1157 */ 1158 public OutputStream getOutputStream() throws FileSystemException { 1159 return getOutputStream(false); 1160 } 1161 1162 /** 1163 * Prepares this file for writing. Makes sure it is either a file, or its parent folder exists. Returns an output 1164 * stream to use to write the content of the file to.<br> 1165 * 1166 * @param bAppend true when append to the file.<br> 1167 * Note: If the underlying filesystem does not support appending, a FileSystemException is thrown. 1168 * @return An OutputStream where the new contents of the file can be written. 1169 * @throws FileSystemException if an error occurs; for example:<br> 1170 * bAppend is true, and the underlying FileSystem does not support it 1171 */ 1172 public OutputStream getOutputStream(final boolean bAppend) throws FileSystemException { 1173 /* 1174 * VFS-210 if (getType() != FileType.IMAGINARY && !getType().hasContent()) { throw new 1175 * FileSystemException("vfs.provider/write-not-file.error", name); } if (!isWriteable()) { throw new 1176 * FileSystemException("vfs.provider/write-read-only.error", name); } 1177 */ 1178 1179 if (bAppend && !fs.hasCapability(Capability.APPEND_CONTENT)) { 1180 throw new FileSystemException("vfs.provider/write-append-not-supported.error", fileName); 1181 } 1182 1183 if (getType() == FileType.IMAGINARY) { 1184 // Does not exist - make sure parent does 1185 final FileObject parent = getParent(); 1186 if (parent != null) { 1187 parent.createFolder(); 1188 } 1189 } 1190 1191 // Get the raw output stream 1192 try { 1193 return doGetOutputStream(bAppend); 1194 } catch (final RuntimeException re) { 1195 throw re; 1196 } catch (final Exception exc) { 1197 throw new FileSystemException("vfs.provider/write.error", exc, fileName); 1198 } 1199 } 1200 1201 /** 1202 * Returns the parent of the file. 1203 * 1204 * @return the parent FileObject. 1205 * @throws FileSystemException if an error occurs. 1206 */ 1207 @Override 1208 public FileObject getParent() throws FileSystemException { 1209 if (this.compareTo(fs.getRoot()) == 0) // equals is not implemented :-/ 1210 { 1211 if (fs.getParentLayer() == null) { 1212 // Root file has no parent 1213 return null; 1214 } 1215 // Return the parent of the parent layer 1216 return fs.getParentLayer().getParent(); 1217 } 1218 1219 synchronized (fs) { 1220 // Locate the parent of this file 1221 if (parent == null) { 1222 final FileName name = fileName.getParent(); 1223 if (name == null) { 1224 return null; 1225 } 1226 parent = fs.resolveFile(name); 1227 } 1228 return parent; 1229 } 1230 } 1231 1232 /** 1233 * Returns an input/output stream to use to read and write the content of the file in and random manner. 1234 * 1235 * @param mode The RandomAccessMode. 1236 * @return The RandomAccessContent. 1237 * @throws FileSystemException if an error occurs. 1238 */ 1239 public RandomAccessContent getRandomAccessContent(final RandomAccessMode mode) throws FileSystemException { 1240 /* 1241 * VFS-210 if (!getType().hasContent()) { throw new FileSystemException("vfs.provider/read-not-file.error", 1242 * name); } 1243 */ 1244 1245 if (mode.requestRead()) { 1246 if (!fs.hasCapability(Capability.RANDOM_ACCESS_READ)) { 1247 throw new FileSystemException("vfs.provider/random-access-read-not-supported.error"); 1248 } 1249 if (!isReadable()) { 1250 throw new FileSystemException("vfs.provider/read-not-readable.error", fileName); 1251 } 1252 } 1253 1254 if (mode.requestWrite()) { 1255 if (!fs.hasCapability(Capability.RANDOM_ACCESS_WRITE)) { 1256 throw new FileSystemException("vfs.provider/random-access-write-not-supported.error"); 1257 } 1258 if (!isWriteable()) { 1259 throw new FileSystemException("vfs.provider/write-read-only.error", fileName); 1260 } 1261 } 1262 1263 // Get the raw input stream 1264 try { 1265 return doGetRandomAccessContent(mode); 1266 } catch (final Exception exc) { 1267 throw new FileSystemException("vfs.provider/random-access.error", fileName, exc); 1268 } 1269 } 1270 1271 /** 1272 * Returns the file's type. 1273 * 1274 * @return The FileType. 1275 * @throws FileSystemException if an error occurs. 1276 */ 1277 @Override 1278 public FileType getType() throws FileSystemException { 1279 synchronized (fs) { 1280 attach(); 1281 1282 // VFS-210: get the type only if requested for 1283 try { 1284 if (type == null) { 1285 setFileType(doGetType()); 1286 } 1287 if (type == null) { 1288 setFileType(FileType.IMAGINARY); 1289 } 1290 } catch (final Exception e) { 1291 throw new FileSystemException("vfs.provider/get-type.error", e, fileName); 1292 } 1293 1294 return type; 1295 } 1296 } 1297 1298 /** 1299 * Returns a URL representation of the file. 1300 * 1301 * @return The URL representation of the file. 1302 * @throws FileSystemException if an error occurs. 1303 */ 1304 @Override 1305 public URL getURL() throws FileSystemException { 1306 try { 1307 return AccessController.doPrivileged(new PrivilegedExceptionAction<URL>() { 1308 @Override 1309 public URL run() throws MalformedURLException { 1310 final StringBuilder buf = new StringBuilder(); 1311 final String scheme = UriParser.extractScheme(fileName.getURI(), buf); 1312 return new URL(scheme, "", -1, buf.toString(), 1313 new DefaultURLStreamHandler(fs.getContext(), fs.getFileSystemOptions())); 1314 } 1315 }); 1316 } catch (final PrivilegedActionException e) { 1317 throw new FileSystemException("vfs.provider/get-url.error", fileName, e.getException()); 1318 } 1319 } 1320 1321 /** 1322 * Called when this file is changed. 1323 * <p> 1324 * This will only happen if you monitor the file using {@link org.apache.commons.vfs2.FileMonitor}. 1325 * 1326 * @throws Exception if an error occurs. 1327 */ 1328 protected void handleChanged() throws Exception { 1329 // Notify the file system 1330 fs.fireFileChanged(this); 1331 } 1332 1333 /** 1334 * Called when this file is created. Updates cached info and notifies the parent and file system. 1335 * 1336 * @param newType The type of the file. 1337 * @throws Exception if an error occurs. 1338 */ 1339 protected void handleCreate(final FileType newType) throws Exception { 1340 synchronized (fs) { 1341 if (attached) { 1342 // Fix up state 1343 injectType(newType); 1344 1345 removeChildrenCache(); 1346 1347 // Notify subclass 1348 onChange(); 1349 } 1350 1351 // Notify parent that its child list may no longer be valid 1352 notifyParent(this.getName(), newType); 1353 1354 // Notify the file system 1355 fs.fireFileCreated(this); 1356 } 1357 } 1358 1359 /** 1360 * Called when this file is deleted. Updates cached info and notifies subclasses, parent and file system. 1361 * 1362 * @throws Exception if an error occurs. 1363 */ 1364 protected void handleDelete() throws Exception { 1365 synchronized (fs) { 1366 if (attached) { 1367 // Fix up state 1368 injectType(FileType.IMAGINARY); 1369 removeChildrenCache(); 1370 1371 // Notify subclass 1372 onChange(); 1373 } 1374 1375 // Notify parent that its child list may no longer be valid 1376 notifyParent(this.getName(), FileType.IMAGINARY); 1377 1378 // Notify the file system 1379 fs.fireFileDeleted(this); 1380 } 1381 } 1382 1383 /** 1384 * This method is meant to add an object where this object holds a strong reference then. E.g. a archive-filesystem 1385 * creates a list of all children and they shouldn't get garbage collected until the container is garbage collected 1386 * 1387 * @param strongRef The Object to add. 1388 */ 1389 // TODO should this be a FileObject? 1390 public void holdObject(final Object strongRef) { 1391 if (objects == null) { 1392 objects = new ArrayList<>(INITIAL_LIST_SIZE); 1393 } 1394 objects.add(strongRef); 1395 } 1396 1397 protected void injectType(final FileType fileType) { 1398 setFileType(fileType); 1399 } 1400 1401 /** 1402 * Check if the internal state is "attached". 1403 * 1404 * @return true if this is the case 1405 */ 1406 @Override 1407 public boolean isAttached() { 1408 return attached; 1409 } 1410 1411 /** 1412 * Check if the content stream is open. 1413 * 1414 * @return true if this is the case 1415 */ 1416 @Override 1417 public boolean isContentOpen() { 1418 if (content == null) { 1419 return false; 1420 } 1421 1422 return content.isOpen(); 1423 } 1424 1425 /** 1426 * Determines if this file is executable. 1427 * 1428 * @return {@code true} if this file is executable, {@code false} if not. 1429 * @throws FileSystemException On error determining if this file exists. 1430 */ 1431 @Override 1432 public boolean isExecutable() throws FileSystemException { 1433 try { 1434 return exists() ? doIsExecutable() : false; 1435 } catch (final Exception exc) { 1436 throw new FileSystemException("vfs.provider/check-is-executable.error", fileName, exc); 1437 } 1438 } 1439 1440 /** 1441 * Checks if this file is a regular file by using its file type. 1442 * 1443 * @return true if this file is a regular file. 1444 * @throws FileSystemException if an error occurs. 1445 * @see #getType() 1446 * @see FileType#FILE 1447 */ 1448 @Override 1449 public boolean isFile() throws FileSystemException { 1450 // Use equals instead of == to avoid any class loader worries. 1451 return FileType.FILE.equals(this.getType()); 1452 } 1453 1454 /** 1455 * Checks if this file is a folder by using its file type. 1456 * 1457 * @return true if this file is a regular file. 1458 * @throws FileSystemException if an error occurs. 1459 * @see #getType() 1460 * @see FileType#FOLDER 1461 */ 1462 @Override 1463 public boolean isFolder() throws FileSystemException { 1464 // Use equals instead of == to avoid any class loader worries. 1465 return FileType.FOLDER.equals(this.getType()); 1466 } 1467 1468 /** 1469 * Determines if this file can be read. 1470 * 1471 * @return true if the file is a hidden file, false otherwise. 1472 * @throws FileSystemException if an error occurs. 1473 */ 1474 @Override 1475 public boolean isHidden() throws FileSystemException { 1476 try { 1477 return exists() ? doIsHidden() : false; 1478 } catch (final Exception exc) { 1479 throw new FileSystemException("vfs.provider/check-is-hidden.error", fileName, exc); 1480 } 1481 } 1482 1483 /** 1484 * Determines if this file can be read. 1485 * 1486 * @return true if the file can be read, false otherwise. 1487 * @throws FileSystemException if an error occurs. 1488 */ 1489 @Override 1490 public boolean isReadable() throws FileSystemException { 1491 try { 1492 return exists() ? doIsReadable() : false; 1493 } catch (final Exception exc) { 1494 throw new FileSystemException("vfs.provider/check-is-readable.error", fileName, exc); 1495 } 1496 } 1497 1498 /** 1499 * Checks if this fileObject is the same file as {@code destFile} just with a different name. E.g. for case 1500 * insensitive filesystems like windows. 1501 * 1502 * @param destFile The file to compare to. 1503 * @return true if the FileObjects are the same. 1504 * @throws FileSystemException if an error occurs. 1505 */ 1506 protected boolean isSameFile(final FileObject destFile) throws FileSystemException { 1507 attach(); 1508 return doIsSameFile(destFile); 1509 } 1510 1511 /** 1512 * Determines if this file can be written to. 1513 * 1514 * @return true if the file can be written to, false otherwise. 1515 * @throws FileSystemException if an error occurs. 1516 */ 1517 @Override 1518 public boolean isWriteable() throws FileSystemException { 1519 try { 1520 if (exists()) { 1521 return doIsWriteable(); 1522 } 1523 final FileObject parent = getParent(); 1524 if (parent != null) { 1525 return parent.isWriteable(); 1526 } 1527 return true; 1528 } catch (final Exception exc) { 1529 throw new FileSystemException("vfs.provider/check-is-writeable.error", fileName, exc); 1530 } 1531 } 1532 1533 /** 1534 * Returns an iterator over a set of all FileObject in this file object. 1535 * 1536 * @return an Iterator. 1537 */ 1538 @Override 1539 public Iterator<FileObject> iterator() { 1540 try { 1541 return listFiles(Selectors.SELECT_ALL).iterator(); 1542 } catch (final FileSystemException e) { 1543 throw new IllegalStateException(e); 1544 } 1545 } 1546 1547 /** 1548 * Lists the set of matching descendants of this file, in depthwise order. 1549 * 1550 * @param selector The FileSelector. 1551 * @return list of files or null if the base file (this object) do not exist or the {@code selector} is null 1552 * @throws FileSystemException if an error occurs. 1553 */ 1554 public List<FileObject> listFiles(final FileSelector selector) throws FileSystemException { 1555 if (!exists() || selector == null) { 1556 return null; 1557 } 1558 1559 final ArrayList<FileObject> list = new ArrayList<>(); 1560 this.findFiles(selector, true, list); 1561 return list; 1562 } 1563 1564 /** 1565 * Moves (rename) the file to another one. 1566 * 1567 * @param destFile The target FileObject. 1568 * @throws FileSystemException if an error occurs. 1569 */ 1570 @Override 1571 public void moveTo(final FileObject destFile) throws FileSystemException { 1572 if (canRenameTo(destFile)) { 1573 if (!getParent().isWriteable()) { 1574 throw new FileSystemException("vfs.provider/rename-parent-read-only.error", getName(), 1575 getParent().getName()); 1576 } 1577 } else { 1578 if (!isWriteable()) { 1579 throw new FileSystemException("vfs.provider/rename-read-only.error", getName()); 1580 } 1581 } 1582 1583 if (destFile.exists() && !isSameFile(destFile)) { 1584 destFile.deleteAll(); 1585 // throw new FileSystemException("vfs.provider/rename-dest-exists.error", destFile.getName()); 1586 } 1587 1588 if (canRenameTo(destFile)) { 1589 // issue rename on same filesystem 1590 try { 1591 attach(); 1592 // remember type to avoid attach 1593 final FileType srcType = getType(); 1594 1595 doRename(destFile); 1596 1597 FileObjectUtils.getAbstractFileObject(destFile).handleCreate(srcType); 1598 destFile.close(); // now the destFile is no longer imaginary. force reattach. 1599 1600 handleDelete(); // fire delete-events. This file-object (src) is like deleted. 1601 } catch (final RuntimeException re) { 1602 throw re; 1603 } catch (final Exception exc) { 1604 throw new FileSystemException("vfs.provider/rename.error", exc, getName(), destFile.getName()); 1605 } 1606 } else { 1607 // different fs - do the copy/delete stuff 1608 1609 destFile.copyFrom(this, Selectors.SELECT_SELF); 1610 1611 if ((destFile.getType().hasContent() 1612 && destFile.getFileSystem().hasCapability(Capability.SET_LAST_MODIFIED_FILE) 1613 || destFile.getType().hasChildren() 1614 && destFile.getFileSystem().hasCapability(Capability.SET_LAST_MODIFIED_FOLDER)) 1615 && fs.hasCapability(Capability.GET_LAST_MODIFIED)) { 1616 destFile.getContent().setLastModifiedTime(this.getContent().getLastModifiedTime()); 1617 } 1618 1619 deleteSelf(); 1620 } 1621 1622 } 1623 1624 /** 1625 * will be called after this file-object closed all its streams. 1626 */ 1627 protected void notifyAllStreamsClosed() { 1628 } 1629 1630 /** 1631 * Notify the parent of a change to its children, when a child is created or deleted. 1632 * 1633 * @param childName The name of the child. 1634 * @param newType The type of the child. 1635 * @throws Exception if an error occurs. 1636 */ 1637 private void notifyParent(final FileName childName, final FileType newType) throws Exception { 1638 if (parent == null) { 1639 final FileName parentName = fileName.getParent(); 1640 if (parentName != null) { 1641 // Locate the parent, if it is cached 1642 parent = fs.getFileFromCache(parentName); 1643 } 1644 } 1645 1646 if (parent != null) { 1647 FileObjectUtils.getAbstractFileObject(parent).childrenChanged(childName, newType); 1648 } 1649 } 1650 1651 /** 1652 * Called when the type or content of this file changes. 1653 * <p> 1654 * This implementation does nothing. 1655 * 1656 * @throws Exception if an error occurs. 1657 */ 1658 protected void onChange() throws Exception { 1659 } 1660 1661 /** 1662 * Called when the children of this file change. Allows subclasses to refresh any cached information about the 1663 * children of this file. 1664 * <p> 1665 * This implementation does nothing. 1666 * 1667 * @param child The name of the child that changed. 1668 * @param newType The type of the file. 1669 * @throws Exception if an error occurs. 1670 */ 1671 protected void onChildrenChanged(final FileName child, final FileType newType) throws Exception { 1672 } 1673 1674 /** 1675 * This will prepare the fileObject to get resynchronized with the underlying filesystem if required. 1676 * 1677 * @throws FileSystemException if an error occurs. 1678 */ 1679 @Override 1680 public void refresh() throws FileSystemException { 1681 // Detach from the file 1682 try { 1683 detach(); 1684 } catch (final Exception e) { 1685 throw new FileSystemException("vfs.provider/resync.error", fileName, e); 1686 } 1687 } 1688 1689 private void removeChildrenCache() { 1690 children = null; 1691 } 1692 1693 private FileObject resolveFile(final FileName child) throws FileSystemException { 1694 return fs.resolveFile(child); 1695 } 1696 1697 /** 1698 * Finds a file, relative to this file. 1699 * 1700 * @param path The path of the file to locate. Can either be a relative path, which is resolved relative to this 1701 * file, or an absolute path, which is resolved relative to the file system that contains this file. 1702 * @return The FileObject. 1703 * @throws FileSystemException if an error occurs. 1704 */ 1705 @Override 1706 public FileObject resolveFile(final String path) throws FileSystemException { 1707 final FileName otherName = fs.getFileSystemManager().resolveName(fileName, path); 1708 return fs.resolveFile(otherName); 1709 } 1710 1711 /** 1712 * Returns a child by name. 1713 * 1714 * @param name The name of the child to locate. 1715 * @param scope the NameScope. 1716 * @return The FileObject for the file or null if the child does not exist. 1717 * @throws FileSystemException if an error occurs. 1718 */ 1719 @Override 1720 public FileObject resolveFile(final String name, final NameScope scope) throws FileSystemException { 1721 // return fs.resolveFile(this.name.resolveName(name, scope)); 1722 return fs.resolveFile(fs.getFileSystemManager().resolveName(this.fileName, name, scope)); 1723 } 1724 1725 private FileObject[] resolveFiles(final FileName[] children) throws FileSystemException { 1726 if (children == null) { 1727 return null; 1728 } 1729 1730 final FileObject[] objects = new FileObject[children.length]; 1731 for (int iterChildren = 0; iterChildren < children.length; iterChildren++) { 1732 objects[iterChildren] = resolveFile(children[iterChildren]); 1733 } 1734 1735 return objects; 1736 } 1737 1738 @Override 1739 public boolean setExecutable(final boolean readable, final boolean ownerOnly) throws FileSystemException { 1740 try { 1741 return exists() ? doSetExecutable(readable, ownerOnly) : false; 1742 } catch (final Exception exc) { 1743 throw new FileSystemException("vfs.provider/set-executable.error", fileName, exc); 1744 } 1745 } 1746 1747 private void setFileType(final FileType type) { 1748 if (type != null && type != FileType.IMAGINARY) { 1749 try { 1750 fileName.setType(type); 1751 } catch (final FileSystemException e) { 1752 throw new RuntimeException(e.getMessage()); 1753 } 1754 } 1755 this.type = type; 1756 } 1757 1758 @Override 1759 public boolean setReadable(final boolean readable, final boolean ownerOnly) throws FileSystemException { 1760 try { 1761 return exists() ? doSetReadable(readable, ownerOnly) : false; 1762 } catch (final Exception exc) { 1763 throw new FileSystemException("vfs.provider/set-readable.error", fileName, exc); 1764 } 1765 } 1766 1767 // --- OPERATIONS --- 1768 1769 @Override 1770 public boolean setWritable(final boolean readable, final boolean ownerOnly) throws FileSystemException { 1771 try { 1772 return exists() ? doSetWritable(readable, ownerOnly) : false; 1773 } catch (final Exception exc) { 1774 throw new FileSystemException("vfs.provider/set-writeable.error", fileName, exc); 1775 } 1776 } 1777 1778 /** 1779 * Returns the URI as a String. 1780 * 1781 * @return Returns the URI as a String. 1782 */ 1783 @Override 1784 public String toString() { 1785 return fileName.getURI(); 1786 } 1787}