1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.vfs.provider;
18
19 import org.apache.commons.vfs.Capability;
20 import org.apache.commons.vfs.FileContent;
21 import org.apache.commons.vfs.FileContentInfoFactory;
22 import org.apache.commons.vfs.FileName;
23 import org.apache.commons.vfs.FileObject;
24 import org.apache.commons.vfs.FileSelector;
25 import org.apache.commons.vfs.FileSystem;
26 import org.apache.commons.vfs.FileSystemException;
27 import org.apache.commons.vfs.FileType;
28 import org.apache.commons.vfs.FileUtil;
29 import org.apache.commons.vfs.NameScope;
30 import org.apache.commons.vfs.RandomAccessContent;
31 import org.apache.commons.vfs.Selectors;
32 import org.apache.commons.vfs.operations.DefaultFileOperations;
33 import org.apache.commons.vfs.operations.FileOperations;
34 import org.apache.commons.vfs.util.FileObjectUtils;
35 import org.apache.commons.vfs.util.RandomAccessMode;
36
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.io.OutputStream;
40 import java.net.MalformedURLException;
41 import java.net.URL;
42 import java.security.AccessController;
43 import java.security.PrivilegedActionException;
44 import java.security.PrivilegedExceptionAction;
45 import java.security.cert.Certificate;
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 import java.util.Collections;
49 import java.util.List;
50 import java.util.Map;
51
52 /***
53 * A partial file object implementation.
54 *
55 * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
56 * @author Gary D. Gregory
57 * @version $Revision: 484648 $ $Date: 2006-12-08 17:18:36 +0100 (Fr, 08 Dez 2006) $
58 * @todo Chop this class up - move all the protected methods to several
59 * interfaces, so that structure and content can be separately overridden.
60 * @todo Check caps in methods like getChildren(), etc, and give better error messages
61 * (eg 'this file type does not support listing children', vs 'this is not a folder')
62 */
63 public abstract class AbstractFileObject implements FileObject
64 {
65
66 private static final FileName[] EMPTY_FILE_ARRAY = {};
67
68 private final AbstractFileName name;
69 private final AbstractFileSystem fs;
70
71 private DefaultFileContent content;
72
73
74 private boolean attached;
75 private FileType type;
76 private FileObject parent;
77
78
79
80
81 private FileName[] children;
82 private List objects;
83
84 /***
85 * FileServices instance.
86 */
87 private FileOperations operations;
88
89 protected AbstractFileObject(final FileName name,
90 final AbstractFileSystem fs)
91 {
92 this.name = (AbstractFileName) name;
93 this.fs = fs;
94 fs.fileObjectHanded(this);
95 }
96
97 /***
98 * Attaches this file object to its file resource. This method is called
99 * before any of the doBlah() or onBlah() methods. Sub-classes can use
100 * this method to perform lazy initialisation.
101 * <p/>
102 * This implementation does nothing.
103 */
104 protected void doAttach() throws Exception
105 {
106 }
107
108 /***
109 * Detaches this file object from its file resource.
110 * <p/>
111 * <p>Called when this file is closed. Note that the file object may be
112 * reused later, so should be able to be reattached.
113 * <p/>
114 * This implementation does nothing.
115 */
116 protected void doDetach() throws Exception
117 {
118 }
119
120 /***
121 * Determines the type of this file. Must not return null. The return
122 * value of this method is cached, so the implementation can be expensive.
123 */
124 protected abstract FileType doGetType() throws Exception;
125
126 /***
127 * Determines if this file is hidden. Is only called if {@link #doGetType}
128 * does not return {@link FileType#IMAGINARY}.
129 * <p/>
130 * This implementation always returns false.
131 */
132 protected boolean doIsHidden() throws Exception
133 {
134 return false;
135 }
136
137 /***
138 * Determines if this file can be read. Is only called if {@link #doGetType}
139 * does not return {@link FileType#IMAGINARY}.
140 * <p/>
141 * This implementation always returns true.
142 */
143 protected boolean doIsReadable() throws Exception
144 {
145 return true;
146 }
147
148 /***
149 * Determines if this file can be written to. Is only called if
150 * {@link #doGetType} does not return {@link FileType#IMAGINARY}.
151 * <p/>
152 * This implementation always returns true.
153 */
154 protected boolean doIsWriteable() throws Exception
155 {
156 return true;
157 }
158
159 /***
160 * Lists the children of this file. Is only called if {@link #doGetType}
161 * returns {@link FileType#FOLDER}. The return value of this method
162 * is cached, so the implementation can be expensive.
163 */
164 protected abstract String[] doListChildren() throws Exception;
165
166 /***
167 * Lists the children of this file. Is only called if {@link #doGetType}
168 * returns {@link FileType#FOLDER}. The return value of this method
169 * is cached, so the implementation can be expensive.<br>
170 * Other than <code>doListChildren</code> you could return FileObject's to e.g. reinitialize the type of the file.<br>
171 * (Introduced for Webdav: "permission denied on resource" during getType())
172 */
173 protected FileObject[] doListChildrenResolved() throws Exception
174 {
175 return null;
176 }
177
178 /***
179 * Deletes the file. Is only called when:
180 * <ul>
181 * <li>{@link #doGetType} does not return {@link FileType#IMAGINARY}.
182 * <li>{@link #doIsWriteable} returns true.
183 * <li>This file has no children, if a folder.
184 * </ul>
185 * <p/>
186 * This implementation throws an exception.
187 */
188 protected void doDelete() throws Exception
189 {
190 throw new FileSystemException("vfs.provider/delete-not-supported.error");
191 }
192
193 /***
194 * Renames the file. Is only called when:
195 * <ul>
196 * <li>{@link #doIsWriteable} returns true.
197 * </ul>
198 * <p/>
199 * This implementation throws an exception.
200 */
201 protected void doRename(FileObject newfile) throws Exception
202 {
203 throw new FileSystemException("vfs.provider/rename-not-supported.error");
204 }
205
206 /***
207 * Creates this file as a folder. Is only called when:
208 * <ul>
209 * <li>{@link #doGetType} returns {@link FileType#IMAGINARY}.
210 * <li>The parent folder exists and is writeable, or this file is the
211 * root of the file system.
212 * </ul>
213 * <p/>
214 * This implementation throws an exception.
215 */
216 protected void doCreateFolder() throws Exception
217 {
218 throw new FileSystemException("vfs.provider/create-folder-not-supported.error");
219 }
220
221 /***
222 * Called when the children of this file change. Allows subclasses to
223 * refresh any cached information about the children of this file.
224 * <p/>
225 * This implementation does nothing.
226 */
227 protected void onChildrenChanged(FileName child, FileType newType) throws Exception
228 {
229 }
230
231 /***
232 * Called when the type or content of this file changes.
233 * <p/>
234 * This implementation does nothing.
235 */
236 protected void onChange() throws Exception
237 {
238 }
239
240 /***
241 * Returns the last modified time of this file. Is only called if
242 * {@link #doGetType} does not return {@link FileType#IMAGINARY}.
243 * <p/>
244 * This implementation throws an exception.
245 */
246 protected long doGetLastModifiedTime() throws Exception
247 {
248 throw new FileSystemException("vfs.provider/get-last-modified-not-supported.error");
249 }
250
251 /***
252 * Sets the last modified time of this file. Is only called if
253 * {@link #doGetType} does not return {@link FileType#IMAGINARY}.
254 * <p/>
255 * This implementation throws an exception.
256 */
257 protected void doSetLastModifiedTime(final long modtime)
258 throws Exception
259 {
260 throw new FileSystemException("vfs.provider/set-last-modified-not-supported.error");
261 }
262
263 /***
264 * Returns the attributes of this file. Is only called if {@link #doGetType}
265 * does not return {@link FileType#IMAGINARY}.
266 * <p/>
267 * This implementation always returns an empty map.
268 */
269 protected Map doGetAttributes()
270 throws Exception
271 {
272 return Collections.EMPTY_MAP;
273 }
274
275 /***
276 * Sets an attribute of this file. Is only called if {@link #doGetType}
277 * does not return {@link FileType#IMAGINARY}.
278 * <p/>
279 * This implementation throws an exception.
280 */
281 protected void doSetAttribute(final String atttrName, final Object value)
282 throws Exception
283 {
284 throw new FileSystemException("vfs.provider/set-attribute-not-supported.error");
285 }
286
287 /***
288 * Returns the certificates used to sign this file. Is only called if
289 * {@link #doGetType} does not return {@link FileType#IMAGINARY}.
290 * <p/>
291 * This implementation always returns null.
292 */
293 protected Certificate[] doGetCertificates() throws Exception
294 {
295 return null;
296 }
297
298 /***
299 * Returns the size of the file content (in bytes). Is only called if
300 * {@link #doGetType} returns {@link FileType#FILE}.
301 */
302 protected abstract long doGetContentSize() throws Exception;
303
304 /***
305 * Creates an input stream to read the file content from. Is only called
306 * if {@link #doGetType} returns {@link FileType#FILE}.
307 * <p/>
308 * <p>It is guaranteed that there are no open output streams for this file
309 * when this method is called.
310 * <p/>
311 * <p>The returned stream does not have to be buffered.
312 */
313 protected abstract InputStream doGetInputStream() throws Exception;
314
315 /***
316 * Creates access to the file for random i/o. Is only called
317 * if {@link #doGetType} returns {@link FileType#FILE}.
318 * <p/>
319 * <p>It is guaranteed that there are no open output streams for this file
320 * when this method is called.
321 * <p/>
322 */
323 protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception
324 {
325 throw new FileSystemException("vfs.provider/random-access-not-supported.error");
326 }
327
328 /***
329 * Creates an output stream to write the file content to. Is only
330 * called if:
331 * <ul>
332 * <li>{@link #doIsWriteable} returns true.
333 * <li>{@link #doGetType} returns {@link FileType#FILE}, or
334 * {@link #doGetType} returns {@link FileType#IMAGINARY}, and the file's
335 * parent exists and is a folder.
336 * </ul>
337 * <p/>
338 * <p>It is guaranteed that there are no open stream (input or output) for
339 * this file when this method is called.
340 * <p/>
341 * <p>The returned stream does not have to be buffered.
342 * <p/>
343 * This implementation throws an exception.
344 */
345 protected OutputStream doGetOutputStream(boolean bAppend) throws Exception
346 {
347 throw new FileSystemException("vfs.provider/write-not-supported.error");
348 }
349
350 /***
351 * Returns the URI of the file.
352 */
353 public String toString()
354 {
355 return name.getURI();
356 }
357
358 /***
359 * Returns the name of the file.
360 */
361 public FileName getName()
362 {
363 return name;
364 }
365
366 /***
367 * Returns the file system this file belongs to.
368 */
369 public FileSystem getFileSystem()
370 {
371 return fs;
372 }
373
374 /***
375 * Returns a URL representation of the file.
376 */
377 public URL getURL() throws FileSystemException
378 {
379 final StringBuffer buf = new StringBuffer();
380 try
381 {
382 return (URL) AccessController.doPrivileged(new PrivilegedExceptionAction()
383 {
384 public Object run() throws MalformedURLException
385 {
386 return new URL(UriParser.extractScheme(name.getURI(), buf), "", -1,
387 buf.toString(), new DefaultURLStreamHandler(fs.getContext(), fs.getFileSystemOptions()));
388 }
389 });
390 }
391 catch (final PrivilegedActionException e)
392 {
393 throw new FileSystemException("vfs.provider/get-url.error", name, e.getException());
394 }
395 }
396
397 /***
398 * Determines if the file exists.
399 */
400 public boolean exists() throws FileSystemException
401 {
402 return (getType() != FileType.IMAGINARY);
403 }
404
405 /***
406 * Returns the file's type.
407 */
408 public FileType getType() throws FileSystemException
409 {
410 synchronized (fs)
411 {
412 attach();
413 return type;
414 }
415 }
416
417 /***
418 * Determines if this file can be read.
419 */
420 public boolean isHidden() throws FileSystemException
421 {
422 try
423 {
424 if (exists())
425 {
426 return doIsHidden();
427 }
428 else
429 {
430 return false;
431 }
432 }
433 catch (final Exception exc)
434 {
435 throw new FileSystemException("vfs.provider/check-is-hidden.error", name, exc);
436 }
437 }
438
439 /***
440 * Determines if this file can be read.
441 */
442 public boolean isReadable() throws FileSystemException
443 {
444 try
445 {
446 if (exists())
447 {
448 return doIsReadable();
449 }
450 else
451 {
452 return false;
453 }
454 }
455 catch (final Exception exc)
456 {
457 throw new FileSystemException("vfs.provider/check-is-readable.error", name, exc);
458 }
459 }
460
461 /***
462 * Determines if this file can be written to.
463 */
464 public boolean isWriteable() throws FileSystemException
465 {
466 try
467 {
468 if (exists())
469 {
470 return doIsWriteable();
471 }
472 else
473 {
474 final FileObject parent = getParent();
475 if (parent != null)
476 {
477 return parent.isWriteable();
478 }
479 return true;
480 }
481 }
482 catch (final Exception exc)
483 {
484 throw new FileSystemException("vfs.provider/check-is-writeable.error", name, exc);
485 }
486 }
487
488 /***
489 * Returns the parent of the file.
490 */
491 public FileObject getParent() throws FileSystemException
492 {
493 if (this == fs.getRoot())
494 {
495 if (fs.getParentLayer() != null)
496 {
497
498 return fs.getParentLayer().getParent();
499 }
500 else
501 {
502
503 return null;
504 }
505 }
506
507 synchronized (fs)
508 {
509
510 if (parent == null)
511 {
512 parent = (FileObject) fs.resolveFile(name.getParent());
513 }
514 }
515 return parent;
516 }
517
518 /***
519 * Returns the children of the file.
520 */
521 public FileObject[] getChildren() throws FileSystemException
522 {
523 synchronized (fs)
524 {
525 if (!getType().hasChildren())
526 {
527 throw new FileSystemException("vfs.provider/list-children-not-folder.error", name);
528 }
529
530
531 if (children != null)
532 {
533 return resolveFiles(children);
534 }
535
536
537 FileObject[] childrenObjects;
538 try
539 {
540 childrenObjects = doListChildrenResolved();
541 children = extractNames(childrenObjects);
542 }
543 catch (Exception exc)
544 {
545 throw new FileSystemException("vfs.provider/list-children.error", new Object[]{name}, exc);
546 }
547
548 if (childrenObjects != null)
549 {
550 return childrenObjects;
551 }
552
553
554 final String[] files;
555 try
556 {
557 files = doListChildren();
558 }
559 catch (Exception exc)
560 {
561 throw new FileSystemException("vfs.provider/list-children.error", new Object[]{name}, exc);
562 }
563
564 if (files == null)
565 {
566 return null;
567 }
568 else if (files.length == 0)
569 {
570
571 children = EMPTY_FILE_ARRAY;
572 }
573 else
574 {
575
576
577 children = new FileName[files.length];
578 for (int i = 0; i < files.length; i++)
579 {
580 final String file = files[i];
581
582
583 children[i] = getFileSystem().getFileSystemManager().resolveName(name, file, NameScope.CHILD);
584 }
585 }
586
587 return resolveFiles(children);
588 }
589 }
590
591 private FileName[] extractNames(FileObject[] objects)
592 {
593 if (objects == null)
594 {
595 return null;
596 }
597
598 FileName[] names = new FileName[objects.length];
599 for (int iterObjects = 0; iterObjects < objects.length; iterObjects++)
600 {
601 names[iterObjects] = objects[iterObjects].getName();
602 }
603
604 return names;
605 }
606
607 private FileObject[] resolveFiles(FileName[] children) throws FileSystemException
608 {
609 if (children == null)
610 {
611 return null;
612 }
613
614 FileObject[] objects = new FileObject[children.length];
615 for (int iterChildren = 0; iterChildren < children.length; iterChildren++)
616 {
617 objects[iterChildren] = resolveFile(children[iterChildren]);
618 }
619
620 return objects;
621 }
622
623 private FileObject resolveFile(FileName child) throws FileSystemException
624 {
625 return fs.resolveFile(child);
626 }
627
628 /***
629 * Returns a child of this file.
630 */
631 public FileObject getChild(final String name) throws FileSystemException
632 {
633
634 FileObject[] children = getChildren();
635 for (int i = 0; i < children.length; i++)
636 {
637
638 final FileName child = children[i].getName();
639
640
641 if (child.getBaseName().equals(name))
642 {
643 return resolveFile(child);
644 }
645 }
646 return null;
647 }
648
649 /***
650 * Returns a child by name.
651 */
652 public FileObject resolveFile(final String name, final NameScope scope)
653 throws FileSystemException
654 {
655
656 return fs.resolveFile(getFileSystem().getFileSystemManager().resolveName(this.name, name, scope));
657 }
658
659 /***
660 * Finds a file, relative to this file.
661 *
662 * @param path The path of the file to locate. Can either be a relative
663 * path, which is resolved relative to this file, or an
664 * absolute path, which is resolved relative to the file system
665 * that contains this file.
666 */
667 public FileObject resolveFile(final String path) throws FileSystemException
668 {
669 final FileName otherName = getFileSystem().getFileSystemManager().resolveName(name, path);
670 return fs.resolveFile(otherName);
671 }
672
673 /***
674 * Deletes this file, once all its children have been deleted
675 *
676 * @return true if this file has been deleted
677 */
678 private boolean deleteSelf() throws FileSystemException
679 {
680 synchronized (fs)
681 {
682
683
684
685
686
687
688
689 if (getType() == FileType.IMAGINARY)
690 {
691
692 return false;
693 }
694
695 try
696 {
697
698 doDelete();
699
700
701 handleDelete();
702 }
703 catch (final RuntimeException re)
704 {
705 throw re;
706 }
707 catch (final Exception exc)
708 {
709 throw new FileSystemException("vfs.provider/delete.error", new Object[]{name}, exc);
710 }
711
712 return true;
713 }
714 }
715
716 /***
717 * Deletes this file.
718 *
719 * @return true if this object has been deleted
720 * @todo This will not fail if this is a non-empty folder.
721 */
722 public boolean delete() throws FileSystemException
723 {
724 return delete(Selectors.SELECT_SELF) > 0;
725 }
726
727 /***
728 * Deletes this file, and all children.
729 *
730 * @return the number of deleted files
731 */
732 public int delete(final FileSelector selector) throws FileSystemException
733 {
734 int nuofDeleted = 0;
735
736 if (getType() == FileType.IMAGINARY)
737 {
738
739 return nuofDeleted;
740 }
741
742
743 ArrayList files = new ArrayList();
744 findFiles(selector, true, files);
745
746
747 final int count = files.size();
748 for (int i = 0; i < count; i++)
749 {
750 final AbstractFileObject file = FileObjectUtils.getAbstractFileObject((FileObject) files.get(i));
751
752
753
754 if (file.getType().hasChildren() && file.getChildren().length != 0)
755 {
756
757 continue;
758 }
759
760
761 boolean deleted = file.deleteSelf();
762 if (deleted)
763 {
764 nuofDeleted++;
765 }
766 }
767
768 return nuofDeleted;
769 }
770
771 /***
772 * Creates this file, if it does not exist.
773 */
774 public void createFile() throws FileSystemException
775 {
776 synchronized (fs)
777 {
778 try
779 {
780 if (exists() && !FileType.FILE.equals(getType()))
781 {
782 throw new FileSystemException("vfs.provider/create-file.error", name);
783 }
784
785 if (!exists())
786 {
787 getOutputStream().close();
788 endOutput();
789 }
790 }
791 catch (final RuntimeException re)
792 {
793 throw re;
794 }
795 catch (final Exception e)
796 {
797 throw new FileSystemException("vfs.provider/create-file.error", name, e);
798 }
799 }
800 }
801
802 /***
803 * Creates this folder, if it does not exist. Also creates any ancestor
804 * files which do not exist.
805 */
806 public void createFolder() throws FileSystemException
807 {
808 synchronized (fs)
809 {
810 if (getType().hasChildren())
811 {
812
813 return;
814 }
815 if (getType() != FileType.IMAGINARY)
816 {
817 throw new FileSystemException("vfs.provider/create-folder-mismatched-type.error", name);
818 }
819 if (!isWriteable())
820 {
821 throw new FileSystemException("vfs.provider/create-folder-read-only.error", name);
822 }
823
824
825 final FileObject parent = getParent();
826 if (parent != null)
827 {
828 parent.createFolder();
829 }
830
831 try
832 {
833
834 doCreateFolder();
835
836
837 handleCreate(FileType.FOLDER);
838 }
839 catch (final RuntimeException re)
840 {
841 throw re;
842 }
843 catch (final Exception exc)
844 {
845 throw new FileSystemException("vfs.provider/create-folder.error", name, exc);
846 }
847 }
848 }
849
850 /***
851 * Copies another file to this file.
852 */
853 public void copyFrom(final FileObject file, final FileSelector selector)
854 throws FileSystemException
855 {
856 if (!file.exists())
857 {
858 throw new FileSystemException("vfs.provider/copy-missing-file.error", file);
859 }
860 if (!isWriteable())
861 {
862 throw new FileSystemException("vfs.provider/copy-read-only.error", new Object[]{file.getType(), file.getName(), this}, null);
863 }
864
865
866 final ArrayList files = new ArrayList();
867 file.findFiles(selector, false, files);
868
869
870 final int count = files.size();
871 for (int i = 0; i < count; i++)
872 {
873 final FileObject srcFile = (FileObject) files.get(i);
874
875
876 final String relPath = file.getName().getRelativeName(srcFile.getName());
877 final FileObject destFile = resolveFile(relPath, NameScope.DESCENDENT_OR_SELF);
878
879
880 if (destFile.exists() && destFile.getType() != srcFile.getType())
881 {
882
883
884
885 destFile.delete(Selectors.SELECT_ALL);
886 }
887
888
889 try
890 {
891 if (srcFile.getType().hasContent())
892 {
893 FileUtil.copyContent(srcFile, destFile);
894 }
895 else if (srcFile.getType().hasChildren())
896 {
897 destFile.createFolder();
898 }
899 }
900 catch (final IOException e)
901 {
902 throw new FileSystemException("vfs.provider/copy-file.error", new Object[]{srcFile, destFile}, e);
903 }
904 }
905 }
906
907 /***
908 * Moves (rename) the file to another one
909 */
910 public void moveTo(FileObject destFile) throws FileSystemException
911 {
912 if (canRenameTo(destFile))
913 {
914 if (!getParent().isWriteable())
915 {
916 throw new FileSystemException("vfs.provider/rename-parent-read-only.error", new FileName[]{getName(), getParent().getName()});
917 }
918 }
919 else
920 {
921 if (!isWriteable())
922 {
923 throw new FileSystemException("vfs.provider/rename-read-only.error", getName());
924 }
925 }
926
927 if (destFile.exists() && !isSameFile(destFile))
928 {
929 destFile.delete(Selectors.SELECT_ALL);
930
931 }
932
933 if (canRenameTo(destFile))
934 {
935
936 try
937 {
938 attach();
939 doRename(destFile);
940
941 (FileObjectUtils.getAbstractFileObject(destFile)).handleCreate(getType());
942
943 destFile.close();
944
945 handleDelete();
946 }
947 catch (final RuntimeException re)
948 {
949 throw re;
950 }
951 catch (final Exception exc)
952 {
953 throw new FileSystemException("vfs.provider/rename.error", new Object[]
954 {
955 getName(),
956 destFile.getName()
957 }, exc);
958 }
959 }
960 else
961 {
962
963
964 destFile.copyFrom(this, Selectors.SELECT_SELF);
965
966 if (((destFile.getType().hasContent() && destFile.getFileSystem().hasCapability(Capability.SET_LAST_MODIFIED_FILE)) ||
967 (destFile.getType().hasChildren() && destFile.getFileSystem().hasCapability(Capability.SET_LAST_MODIFIED_FOLDER))) &&
968 getFileSystem().hasCapability(Capability.GET_LAST_MODIFIED))
969 {
970 destFile.getContent().setLastModifiedTime(this.getContent().getLastModifiedTime());
971 }
972
973 deleteSelf();
974 }
975
976 }
977
978 /***
979 * Checks if this fileObject is the same file as <code>destFile</code> just with a different
980 * name.<br />
981 * E.g. for case insensitive filesystems like windows.
982 */
983 protected boolean isSameFile(FileObject destFile) throws FileSystemException
984 {
985 attach();
986 return doIsSameFile(destFile);
987 }
988
989 /***
990 * Checks if this fileObject is the same file as <code>destFile</code> just with a different
991 * name.<br />
992 * E.g. for case insensitive filesystems like windows.
993 */
994 protected boolean doIsSameFile(FileObject destFile) throws FileSystemException
995 {
996 return false;
997 }
998
999 /***
1000 * Queries the object if a simple rename to the filename of <code>newfile</code>
1001 * is possible.
1002 *
1003 * @param newfile the new filename
1004 * @return true if rename is possible
1005 */
1006 public boolean canRenameTo(FileObject newfile)
1007 {
1008 if (getFileSystem() == newfile.getFileSystem())
1009 {
1010 return true;
1011 }
1012
1013 return false;
1014 }
1015
1016 /***
1017 * Finds the set of matching descendents of this file, in depthwise
1018 * order.
1019 *
1020 * @return list of files or null if the base file (this object) do not exist
1021 */
1022 public FileObject[] findFiles(final FileSelector selector) throws FileSystemException
1023 {
1024 if (!exists())
1025 {
1026 return null;
1027 }
1028
1029 final ArrayList list = new ArrayList();
1030 findFiles(selector, true, list);
1031 return (FileObject[]) list.toArray(new FileObject[list.size()]);
1032 }
1033
1034 /***
1035 * Returns the file's content.
1036 */
1037 public FileContent getContent() throws FileSystemException
1038 {
1039 synchronized (fs)
1040 {
1041 attach();
1042 if (content == null)
1043 {
1044 content = new DefaultFileContent(this, getFileContentInfoFactory());
1045 }
1046 return content;
1047 }
1048 }
1049
1050 /***
1051 * This will prepare the fileObject to get resynchronized with the underlaying filesystem if required
1052 */
1053 public void refresh() throws FileSystemException
1054 {
1055
1056 try
1057 {
1058 detach();
1059 }
1060 catch (final Exception e)
1061 {
1062 throw new FileSystemException("vfs.provider/resync.error", name, e);
1063 }
1064 }
1065
1066 /***
1067 * Closes this file, and its content.
1068 */
1069 public void close() throws FileSystemException
1070 {
1071 FileSystemException exc = null;
1072
1073
1074 if (content != null)
1075 {
1076 try
1077 {
1078 content.close();
1079 }
1080 catch (FileSystemException e)
1081 {
1082 exc = e;
1083 }
1084 }
1085
1086
1087 try
1088 {
1089 detach();
1090 }
1091 catch (final Exception e)
1092 {
1093 exc = new FileSystemException("vfs.provider/close.error", name, e);
1094 }
1095
1096 if (exc != null)
1097 {
1098 throw exc;
1099 }
1100 }
1101
1102 /***
1103 * Returns an input stream to use to read the content of the file.
1104 */
1105 public InputStream getInputStream() throws FileSystemException
1106 {
1107 if (!getType().hasContent())
1108 {
1109 throw new FileSystemException("vfs.provider/read-not-file.error", name);
1110 }
1111 if (!isReadable())
1112 {
1113 throw new FileSystemException("vfs.provider/read-not-readable.error", name);
1114 }
1115
1116
1117 try
1118 {
1119 return doGetInputStream();
1120 }
1121 catch (final Exception exc)
1122 {
1123 throw new FileSystemException("vfs.provider/read.error", name, exc);
1124 }
1125 }
1126
1127 /***
1128 * Returns an input/output stream to use to read and write the content of the file in and
1129 * random manner.
1130 */
1131 public RandomAccessContent getRandomAccessContent(final RandomAccessMode mode) throws FileSystemException
1132 {
1133 if (!getType().hasContent())
1134 {
1135 throw new FileSystemException("vfs.provider/read-not-file.error", name);
1136 }
1137
1138 if (mode.requestRead())
1139 {
1140 if (!getFileSystem().hasCapability(Capability.RANDOM_ACCESS_READ))
1141 {
1142 throw new FileSystemException("vfs.provider/random-access-read-not-supported.error");
1143 }
1144 if (!isReadable())
1145 {
1146 throw new FileSystemException("vfs.provider/read-not-readable.error", name);
1147 }
1148 }
1149
1150 if (mode.requestWrite())
1151 {
1152 if (!getFileSystem().hasCapability(Capability.RANDOM_ACCESS_WRITE))
1153 {
1154 throw new FileSystemException("vfs.provider/random-access-write-not-supported.error");
1155 }
1156 if (!isWriteable())
1157 {
1158 throw new FileSystemException("vfs.provider/write-read-only.error", name);
1159 }
1160 }
1161
1162
1163 try
1164 {
1165 return doGetRandomAccessContent(mode);
1166 }
1167 catch (final Exception exc)
1168 {
1169 throw new FileSystemException("vfs.provider/random-access.error", name, exc);
1170 }
1171 }
1172
1173 /***
1174 * Prepares this file for writing. Makes sure it is either a file,
1175 * or its parent folder exists. Returns an output stream to use to
1176 * write the content of the file to.
1177 */
1178 public OutputStream getOutputStream() throws FileSystemException
1179 {
1180 return getOutputStream(false);
1181 }
1182
1183 /***
1184 * Prepares this file for writing. Makes sure it is either a file,
1185 * or its parent folder exists. Returns an output stream to use to
1186 * write the content of the file to.<br>
1187 *
1188 * @param bAppend true when append to the file.<br>
1189 * Note: If the underlaying filesystem do not support this, it wont work.
1190 */
1191 public OutputStream getOutputStream(boolean bAppend) throws FileSystemException
1192 {
1193 if (getType() != FileType.IMAGINARY && !getType().hasContent())
1194 {
1195 throw new FileSystemException("vfs.provider/write-not-file.error", name);
1196 }
1197 if (!isWriteable())
1198 {
1199 throw new FileSystemException("vfs.provider/write-read-only.error", name);
1200 }
1201 if (bAppend && !getFileSystem().hasCapability(Capability.APPEND_CONTENT))
1202 {
1203 throw new FileSystemException("vfs.provider/write-append-not-supported.error", name);
1204 }
1205
1206 if (getType() == FileType.IMAGINARY)
1207 {
1208
1209 FileObject parent = getParent();
1210 if (parent != null)
1211 {
1212 parent.createFolder();
1213 }
1214 }
1215
1216
1217 try
1218 {
1219 return doGetOutputStream(bAppend);
1220 }
1221 catch (RuntimeException re)
1222 {
1223 throw re;
1224 }
1225 catch (Exception exc)
1226 {
1227 throw new FileSystemException("vfs.provider/write.error", new Object[]{name}, exc);
1228 }
1229 }
1230
1231 /***
1232 * Detaches this file, invaliating all cached info. This will force
1233 * a call to {@link #doAttach} next time this file is used.
1234 */
1235 private void detach() throws Exception
1236 {
1237 synchronized (fs)
1238 {
1239 if (attached)
1240 {
1241 try
1242 {
1243 doDetach();
1244 }
1245 finally
1246 {
1247 attached = false;
1248 setFileType(null);
1249 parent = null;
1250
1251
1252
1253 removeChildrenCache();
1254
1255 }
1256 }
1257 }
1258 }
1259
1260 private void removeChildrenCache()
1261 {
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273 children = null;
1274 }
1275
1276 /***
1277 * Attaches to the file.
1278 */
1279 private void attach() throws FileSystemException
1280 {
1281 synchronized (fs)
1282 {
1283 if (attached)
1284 {
1285 return;
1286 }
1287
1288 try
1289 {
1290
1291 doAttach();
1292 attached = true;
1293
1294 if (type == null)
1295 {
1296 setFileType(doGetType());
1297 }
1298 if (type == null)
1299 {
1300 setFileType(FileType.IMAGINARY);
1301 }
1302 }
1303 catch (Exception exc)
1304 {
1305 throw new FileSystemException("vfs.provider/get-type.error", new Object[]{name}, exc);
1306 }
1307
1308
1309 }
1310 }
1311
1312 /***
1313 * Called when the ouput stream for this file is closed.
1314 */
1315 protected void endOutput() throws Exception
1316 {
1317 if (getType() == FileType.IMAGINARY)
1318 {
1319
1320 handleCreate(FileType.FILE);
1321 }
1322 else
1323 {
1324
1325 onChange();
1326 }
1327 }
1328
1329 /***
1330 * Called when this file is created. Updates cached info and notifies
1331 * the parent and file system.
1332 */
1333 protected void handleCreate(final FileType newType) throws Exception
1334 {
1335 synchronized (fs)
1336 {
1337 if (attached)
1338 {
1339
1340 injectType(newType);
1341
1342 removeChildrenCache();
1343
1344
1345
1346 onChange();
1347 }
1348
1349
1350 notifyParent(this.getName(), newType);
1351
1352
1353 fs.fireFileCreated(this);
1354 }
1355 }
1356
1357 /***
1358 * Called when this file is deleted. Updates cached info and notifies
1359 * subclasses, parent and file system.
1360 */
1361 protected void handleDelete() throws Exception
1362 {
1363 synchronized (fs)
1364 {
1365 if (attached)
1366 {
1367
1368 injectType(FileType.IMAGINARY);
1369 removeChildrenCache();
1370
1371
1372
1373 onChange();
1374 }
1375
1376
1377 notifyParent(this.getName(), FileType.IMAGINARY);
1378
1379
1380 fs.fireFileDeleted(this);
1381 }
1382 }
1383
1384 /***
1385 * Called when this file is changed.<br />
1386 * This will only happen if you monitor the file using {@link org.apache.commons.vfs.FileMonitor}.
1387 */
1388 protected void handleChanged() throws Exception
1389 {
1390
1391 fs.fireFileChanged(this);
1392 }
1393
1394 /***
1395 * Notifies the file that its children have changed.
1396 *
1397 * @deprecated use {@link #childrenChanged(FileName,FileType)}
1398 */
1399 protected void childrenChanged() throws Exception
1400 {
1401 childrenChanged(null, null);
1402 }
1403
1404 /***
1405 * Notifies the file that its children have changed.
1406 */
1407 protected void childrenChanged(FileName childName, FileType newType) throws Exception
1408 {
1409
1410
1411 if (children != null)
1412 {
1413 if (childName != null && newType != null)
1414 {
1415
1416 ArrayList list = new ArrayList(Arrays.asList(children));
1417 if (newType.equals(FileType.IMAGINARY))
1418 {
1419 list.remove(childName);
1420 }
1421 else
1422 {
1423 list.add(childName);
1424 }
1425 children = new FileName[list.size()];
1426 list.toArray(children);
1427 }
1428 }
1429
1430
1431 onChildrenChanged(childName, newType);
1432 }
1433
1434 /***
1435 * Notify the parent of a change to its children, when a child is created
1436 * or deleted.
1437 */
1438 private void notifyParent(FileName childName, FileType newType) throws Exception
1439 {
1440 if (parent == null)
1441 {
1442 FileName parentName = name.getParent();
1443 if (parentName != null)
1444 {
1445
1446 parent = fs.getFileFromCache(parentName);
1447 }
1448 }
1449
1450 if (parent != null)
1451 {
1452 FileObjectUtils.getAbstractFileObject(parent).childrenChanged(childName, newType);
1453 }
1454 }
1455
1456 /***
1457 * Traverses the descendents of this file, and builds a list of selected
1458 * files.
1459 */
1460 public void findFiles(final FileSelector selector,
1461 final boolean depthwise,
1462 final List selected) throws FileSystemException
1463 {
1464 try
1465 {
1466 if (exists())
1467 {
1468
1469 final DefaultFileSelectorInfo info = new DefaultFileSelectorInfo();
1470 info.setBaseFolder(this);
1471 info.setDepth(0);
1472 info.setFile(this);
1473 traverse(info, selector, depthwise, selected);
1474 }
1475 }
1476 catch (final Exception e)
1477 {
1478 throw new FileSystemException("vfs.provider/find-files.error", name, e);
1479 }
1480 }
1481
1482 /***
1483 * Traverses a file.
1484 */
1485 private static void traverse(final DefaultFileSelectorInfo fileInfo,
1486 final FileSelector selector,
1487 final boolean depthwise,
1488 final List selected)
1489 throws Exception
1490 {
1491
1492 final FileObject file = fileInfo.getFile();
1493 final int index = selected.size();
1494
1495
1496 if (file.getType().hasChildren() && selector.traverseDescendents(fileInfo))
1497 {
1498 final int curDepth = fileInfo.getDepth();
1499 fileInfo.setDepth(curDepth + 1);
1500
1501
1502 final FileObject[] children = file.getChildren();
1503 for (int i = 0; i < children.length; i++)
1504 {
1505 final FileObject child = children[i];
1506 fileInfo.setFile(child);
1507 traverse(fileInfo, selector, depthwise, selected);
1508 }
1509
1510 fileInfo.setFile(file);
1511 fileInfo.setDepth(curDepth);
1512 }
1513
1514
1515 if (selector.includeFile(fileInfo))
1516 {
1517 if (depthwise)
1518 {
1519
1520 selected.add(file);
1521 }
1522 else
1523 {
1524
1525 selected.add(index, file);
1526 }
1527 }
1528 }
1529
1530 /***
1531 * Check if the content stream is open
1532 *
1533 * @return true if this is the case
1534 */
1535 public boolean isContentOpen()
1536 {
1537 if (content == null)
1538 {
1539 return false;
1540 }
1541
1542 return content.isOpen();
1543 }
1544
1545 /***
1546 * Check if the internal state is "attached"
1547 *
1548 * @return true if this is the case
1549 */
1550 public boolean isAttached()
1551 {
1552 return attached;
1553 }
1554
1555 /***
1556 * create the filecontentinfo implementation
1557 */
1558 protected FileContentInfoFactory getFileContentInfoFactory()
1559 {
1560 return getFileSystem().getFileSystemManager().getFileContentInfoFactory();
1561 }
1562
1563 protected void injectType(FileType fileType)
1564 {
1565 setFileType(fileType);
1566 }
1567
1568 private void setFileType(FileType type)
1569 {
1570 if (type != null && type != FileType.IMAGINARY)
1571 {
1572 try
1573 {
1574 name.setType(type);
1575 }
1576 catch (FileSystemException e)
1577 {
1578 throw new RuntimeException(e.getMessage());
1579 }
1580 }
1581 this.type = type;
1582 }
1583
1584 /***
1585 * This method is meant to add a object where this object holds a strong reference then.
1586 * E.g. a archive-filesystem creates a list of all childs and they shouldnt get
1587 * garbage collected until the container is garbage collected
1588 *
1589 * @param strongRef
1590 */
1591 public void holdObject(Object strongRef)
1592 {
1593 if (objects == null)
1594 {
1595 objects = new ArrayList(5);
1596 }
1597 objects.add(strongRef);
1598 }
1599
1600 /***
1601 * will be called after this file-object closed all its streams.
1602 */
1603 protected void notifyAllStreamsClosed()
1604 {
1605 }
1606
1607
1608
1609 /***
1610 * @return FileOperations interface that provides access to the operations
1611 * API.
1612 * @throws FileSystemException
1613 */
1614 public FileOperations getFileOperations() throws FileSystemException
1615 {
1616 if (operations == null)
1617 {
1618 operations = new DefaultFileOperations(this);
1619 }
1620
1621 return operations;
1622 }
1623
1624 protected void finalize() throws Throwable
1625 {
1626 fs.fileObjectDestroyed(this);
1627
1628 super.finalize();
1629 }
1630 }