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 }