View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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      // private static final FileObject[] EMPTY_FILE_ARRAY = {};
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      // Cached info
74      private boolean attached;
75      private FileType type;
76      private FileObject parent;
77  
78      // Changed to hold only the name of the children and let the object
79      // go into the global files cache
80      // private FileObject[] children;
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                 // Return the parent of the parent layer
498                 return fs.getParentLayer().getParent();
499             }
500             else
501             {
502                 // Root file has no parent
503                 return null;
504             }
505         }
506 
507         synchronized (fs)
508         {
509             // Locate the parent of this file
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             // Use cached info, if present
531             if (children != null)
532             {
533                 return resolveFiles(children);
534             }
535 
536             // allow the filesystem to return resolved children. e.g. prefill type for webdav
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             // List the children
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                 // No children
571                 children = EMPTY_FILE_ARRAY;
572             }
573             else
574             {
575                 // Create file objects for the children
576                 // children = new FileObject[files.length];
577                 children = new FileName[files.length];
578                 for (int i = 0; i < files.length; i++)
579                 {
580                     final String file = files[i];
581                     // children[i] = fs.resolveFile(name.resolveName(file, NameScope.CHILD));
582                     // children[i] = name.resolveName(file, NameScope.CHILD);
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         // TODO - use a hashtable when there are a large number of children
634         FileObject[] children = getChildren();
635         for (int i = 0; i < children.length; i++)
636         {
637             // final FileObject child = children[i];
638             final FileName child = children[i].getName();
639             // TODO - use a comparator to compare names
640             // if (child.getName().getBaseName().equals(name))
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         // return fs.resolveFile(this.name.resolveName(name, scope));
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             /* Its possible to delete a read-only file if you have write-execute access to the directory
683             if (!isWriteable())
684             {
685                 throw new FileSystemException("vfs.provider/delete-read-only.error", name);
686             }
687             */
688 
689             if (getType() == FileType.IMAGINARY)
690             {
691                 // File does not exist
692                 return false;
693             }
694 
695             try
696             {
697                 // Delete the file
698                 doDelete();
699 
700                 // Update cached info
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             // File does not exist
739             return nuofDeleted;
740         }
741 
742         // Locate all the files to delete
743         ArrayList files = new ArrayList();
744         findFiles(selector, true, files);
745 
746         // Delete 'em
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             // file.attach();
752 
753             // If the file is a folder, make sure all its children have been deleted
754             if (file.getType().hasChildren() && file.getChildren().length != 0)
755             {
756                 // Skip - as the selector forced us not to delete all files
757                 continue;
758             }
759 
760             // Delete the file
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                 // Already exists as correct type
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             // Traverse up the heirarchy and make sure everything is a folder
825             final FileObject parent = getParent();
826             if (parent != null)
827             {
828                 parent.createFolder();
829             }
830 
831             try
832             {
833                 // Create the folder
834                 doCreateFolder();
835 
836                 // Update cached info
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         // Locate the files to copy across
866         final ArrayList files = new ArrayList();
867         file.findFiles(selector, false, files);
868 
869         // Copy everything across
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             // Determine the destination file
876             final String relPath = file.getName().getRelativeName(srcFile.getName());
877             final FileObject destFile = resolveFile(relPath, NameScope.DESCENDENT_OR_SELF);
878 
879             // Clean up the destination file, if necessary
880             if (destFile.exists() && destFile.getType() != srcFile.getType())
881             {
882                 // The destination file exists, and is not of the same type,
883                 // so delete it
884                 // TODO - add a pluggable policy for deleting and overwriting existing files
885                 destFile.delete(Selectors.SELECT_ALL);
886             }
887 
888             // Copy across
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             // throw new FileSystemException("vfs.provider/rename-dest-exists.error", destFile.getName());
931         }
932 
933         if (canRenameTo(destFile))
934         {
935             // issue rename on same filesystem
936             try
937             {
938                 attach();
939                 doRename(destFile);
940 
941                 (FileObjectUtils.getAbstractFileObject(destFile)).handleCreate(getType());
942 
943                 destFile.close(); // now the destFile is no longer imaginary. force reattach.
944 
945                 handleDelete(); // fire delete-events. This file-object (src) is like deleted.
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             // different fs - do the copy/delete stuff
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         // Detach from the file
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         // Close the content
1074         if (content != null)
1075         {
1076             try
1077             {
1078                 content.close();
1079             }
1080             catch (FileSystemException e)
1081             {
1082                 exc = e;
1083             }
1084         }
1085 
1086         // Detach from the file
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         // Get the raw input stream
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         // Get the raw input stream
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 // Does not exist - make sure parent does
1209             FileObject parent = getParent();
1210             if (parent != null)
1211             {
1212                 parent.createFolder();
1213             }
1214         }
1215 
1216 // Get the raw output stream
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                     // fs.fileDetached(this);
1252 
1253                     removeChildrenCache();
1254                     // children = null;
1255                 }
1256             }
1257         }
1258     }
1259 
1260     private void removeChildrenCache()
1261     {
1262         /*
1263         if (children != null)
1264         {
1265             for (int iterChildren = 0; iterChildren < children.length; iterChildren++)
1266             {
1267                 fs.removeFileFromCache(children[iterChildren].getName());
1268             }
1269 
1270             children = null;
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                 // Attach and determine the file type
1291                 doAttach();
1292                 attached = true;
1293                 // now the type could already be injected by doAttach (e.g from parent to child)
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             // fs.fileAttached(this);
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             // File was created
1320             handleCreate(FileType.FILE);
1321         }
1322         else
1323         {
1324             // File has changed
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                 // Fix up state
1340                 injectType(newType);
1341 
1342                 removeChildrenCache();
1343                 // children = EMPTY_FILE_ARRAY;
1344 
1345                 // Notify subclass
1346                 onChange();
1347             }
1348 
1349             // Notify parent that its child list may no longer be valid
1350             notifyParent(this.getName(), newType);
1351 
1352             // Notify the file system
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                 // Fix up state
1368                 injectType(FileType.IMAGINARY);
1369                 removeChildrenCache();
1370                 // children = null;
1371 
1372                 // Notify subclass
1373                 onChange();
1374             }
1375 
1376             // Notify parent that its child list may no longer be valid
1377             notifyParent(this.getName(), FileType.IMAGINARY);
1378 
1379             // Notify the file system
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         // Notify the file system
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         // TODO - this may be called when not attached
1410 
1411         if (children != null)
1412         {
1413             if (childName != null && newType != null)
1414             {
1415                 // TODO - figure out if children[] can be replaced by list
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         // removeChildrenCache();
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                 // Locate the parent, if it is cached
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                 // Traverse starting at this file
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         // Check the file itself
1492         final FileObject file = fileInfo.getFile();
1493         final int index = selected.size();
1494 
1495         // If the file is a folder, traverse it
1496         if (file.getType().hasChildren() && selector.traverseDescendents(fileInfo))
1497         {
1498             final int curDepth = fileInfo.getDepth();
1499             fileInfo.setDepth(curDepth + 1);
1500 
1501             // Traverse the children
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         // Add the file if doing depthwise traversal
1515         if (selector.includeFile(fileInfo))
1516         {
1517             if (depthwise)
1518             {
1519                 // Add this file after its descendents
1520                 selected.add(file);
1521             }
1522             else
1523             {
1524                 // Add this file before its descendents
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     // --- OPERATIONS ---
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 }