001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.vfs2.impl;
018
019import java.io.File;
020import java.lang.reflect.Constructor;
021import java.net.URI;
022import java.net.URISyntaxException;
023import java.net.URL;
024import java.net.URLStreamHandler;
025import java.net.URLStreamHandlerFactory;
026import java.util.ArrayList;
027import java.util.Collection;
028import java.util.HashMap;
029import java.util.List;
030import java.util.Map;
031
032import org.apache.commons.logging.Log;
033import org.apache.commons.logging.LogFactory;
034import org.apache.commons.vfs2.CacheStrategy;
035import org.apache.commons.vfs2.Capability;
036import org.apache.commons.vfs2.FileContentInfoFactory;
037import org.apache.commons.vfs2.FileName;
038import org.apache.commons.vfs2.FileObject;
039import org.apache.commons.vfs2.FileSystem;
040import org.apache.commons.vfs2.FileSystemConfigBuilder;
041import org.apache.commons.vfs2.FileSystemException;
042import org.apache.commons.vfs2.FileSystemManager;
043import org.apache.commons.vfs2.FileSystemOptions;
044import org.apache.commons.vfs2.FileType;
045import org.apache.commons.vfs2.FilesCache;
046import org.apache.commons.vfs2.NameScope;
047import org.apache.commons.vfs2.VFS;
048import org.apache.commons.vfs2.cache.SoftRefFilesCache;
049import org.apache.commons.vfs2.operations.FileOperationProvider;
050import org.apache.commons.vfs2.provider.AbstractFileName;
051import org.apache.commons.vfs2.provider.AbstractFileProvider;
052import org.apache.commons.vfs2.provider.DefaultURLStreamHandler;
053import org.apache.commons.vfs2.provider.FileProvider;
054import org.apache.commons.vfs2.provider.FileReplicator;
055import org.apache.commons.vfs2.provider.LocalFileProvider;
056import org.apache.commons.vfs2.provider.TemporaryFileStore;
057import org.apache.commons.vfs2.provider.UriParser;
058import org.apache.commons.vfs2.provider.VfsComponent;
059
060/**
061 * The default file system manager implementation.
062 *
063 * @version $Id: DefaultFileSystemManager.html 899535 2014-02-28 16:53:18Z britter $
064 */
065public class DefaultFileSystemManager implements FileSystemManager
066{
067    /**
068     * The provider for local files.
069     */
070    private LocalFileProvider localFileProvider;
071
072    /**
073     * The default provider.
074     */
075    private FileProvider defaultProvider;
076
077    /**
078     * The file replicator to use.
079     */
080    private FileReplicator fileReplicator;
081
082    /**
083     * Mapping from URI scheme to FileProvider.
084     */
085    private final Map<String, FileProvider> providers = new HashMap<String, FileProvider>();
086
087    /**
088     * All components used by this manager.
089     */
090    private final ArrayList<Object> components = new ArrayList<Object>();
091
092    /**
093     * The base file to use for relative URI.
094     */
095    private FileObject baseFile;
096
097    /**
098     * The files cache
099     */
100    private FilesCache filesCache;
101
102    /**
103     * The cache strategy
104     */
105    private CacheStrategy fileCacheStrategy;
106
107    /**
108     * Class which decorates all returned fileObjects
109     */
110    private Class<?> fileObjectDecorator;
111    private Constructor<?> fileObjectDecoratorConst;
112
113    /**
114     * The class to use to determine the content-type (mime-type)
115     */
116    private FileContentInfoFactory fileContentInfoFactory;
117
118    /**
119     * The logger to use.
120     */
121    private Log log = LogFactory.getLog(getClass());
122
123    /**
124     * The context to pass to providers.
125     */
126    private final DefaultVfsComponentContext context = new DefaultVfsComponentContext(
127            this);
128
129    private TemporaryFileStore tempFileStore;
130    private final FileTypeMap map = new FileTypeMap();
131    private final VirtualFileProvider vfsProvider = new VirtualFileProvider();
132    private boolean init;
133
134    private final Map<String, List<FileOperationProvider>> operationProviders =
135          new HashMap<String, List<FileOperationProvider>>();
136
137    /**
138     * Returns the logger used by this manager.
139     * @return the Logger.
140     */
141    protected Log getLogger()
142    {
143        return log;
144    }
145
146    /**
147     * Registers a file system provider. The manager takes care of all lifecycle
148     * management. A provider may be registered multiple times.
149     *
150     * @param urlScheme
151     *            The scheme the provider will handle.
152     * @param provider
153     *            The provider.
154     * @throws FileSystemException if an error occurs adding the provider.
155     */
156    public void addProvider(final String urlScheme, final FileProvider provider)
157            throws FileSystemException
158    {
159        addProvider(new String[] {urlScheme}, provider);
160    }
161
162    /**
163     * Registers a file system provider. The manager takes care of all lifecycle
164     * management. A provider may be registered multiple times.
165     *
166     * @param urlSchemes
167     *            The schemes the provider will handle.
168     * @param provider
169     *            The provider.
170     * @throws FileSystemException if an error occurs adding the provider.
171     */
172    public void addProvider(final String[] urlSchemes,
173            final FileProvider provider) throws FileSystemException
174    {
175        // Warn about duplicate providers
176        for (final String scheme : urlSchemes)
177        {
178            if (providers.containsKey(scheme))
179            {
180                throw new FileSystemException(
181                        "vfs.impl/multiple-providers-for-scheme.error", scheme);
182            }
183        }
184
185        // Contextualise the component (if not already)
186        setupComponent(provider);
187
188        // Add to map
189        for (final String scheme : urlSchemes)
190        {
191            providers.put(scheme, provider);
192        }
193
194        if (provider instanceof LocalFileProvider && localFileProvider == null)
195        {
196            localFileProvider = (LocalFileProvider) provider;
197        }
198    }
199
200    /**
201     * Returns true if this manager has a provider for a particular scheme.
202     * @param scheme The scheme to check.
203     * @return true if a provider is configured for this scheme, false otherwise.
204     */
205    @Override
206    public boolean hasProvider(final String scheme)
207    {
208        return providers.containsKey(scheme);
209    }
210
211    /**
212     * Adds an filename extension mapping.
213     *
214     * @param extension The file name extension.
215     * @param scheme The scheme to use for files with this extension.
216     */
217    public void addExtensionMap(final String extension, final String scheme)
218    {
219        map.addExtension(extension, scheme);
220    }
221
222    /**
223     * Adds a mime type mapping.
224     *
225     * @param mimeType The mime type.
226     * @param scheme The scheme to use for files with this mime type.
227     */
228    public void addMimeTypeMap(final String mimeType, final String scheme)
229    {
230        map.addMimeType(mimeType, scheme);
231    }
232
233    /**
234     * Sets the default provider. This is the provider that will handle URI with
235     * unknown schemes. The manager takes care of all lifecycle management.
236     * @param provider The FileProvider.
237     * @throws FileSystemException if an error occurs setting the provider.
238     */
239    public void setDefaultProvider(final FileProvider provider)
240            throws FileSystemException
241    {
242        setupComponent(provider);
243        defaultProvider = provider;
244    }
245
246    /**
247     * Returns the filesCache implementation used to cache files.
248     * @return The FilesCache.
249     */
250    @Override
251    public FilesCache getFilesCache()
252    {
253        return filesCache;
254    }
255
256    /**
257     * Sets the filesCache implementation used to cache files.
258     * @param filesCache The FilesCache.
259     * @throws FileSystemException if an error occurs setting the cache..
260     */
261    public void setFilesCache(final FilesCache filesCache)
262            throws FileSystemException
263    {
264        if (init)
265        {
266            throw new FileSystemException("vfs.impl/already-inited.error");
267        }
268
269        this.filesCache = filesCache;
270    }
271
272    /**
273     * <p>
274     * Set the cache strategy to use when dealing with file object data. You can
275     * set it only once before the FileSystemManager is initialized.
276     * <p />
277     * <p>
278     * The default is {@link CacheStrategy#ON_RESOLVE}
279     * </p>
280     *
281     * @param fileCacheStrategy The CacheStrategy to use.
282     * @throws FileSystemException
283     *             if this is not possible. e.g. it is already set.
284     */
285    public void setCacheStrategy(final CacheStrategy fileCacheStrategy)
286            throws FileSystemException
287    {
288        if (init)
289        {
290            throw new FileSystemException("vfs.impl/already-inited.error");
291        }
292
293        this.fileCacheStrategy = fileCacheStrategy;
294    }
295
296    /**
297     * Get the cache strategy used.
298     * @return The CacheStrategy.
299     */
300    @Override
301    public CacheStrategy getCacheStrategy()
302    {
303        return fileCacheStrategy;
304    }
305
306    /**
307     * Get the file object decorator used.
308     * @return The decorator.
309     */
310    @Override
311    public Class<?> getFileObjectDecorator()
312    {
313        return fileObjectDecorator;
314    }
315
316    /**
317     * The constructor associated to the fileObjectDecorator.
318     * We cache it here for performance reasons.
319     * @return The decorator's Constructor.
320     */
321    @Override
322    public Constructor<?> getFileObjectDecoratorConst()
323    {
324        return fileObjectDecoratorConst;
325    }
326
327    /**
328     * Set a fileObject decorator to be used for ALL returned file objects.
329     *
330     * @param fileObjectDecorator must be inherted from {@link DecoratedFileObject} a has to provide a
331     * constructor with a single {@link FileObject} as argument
332     * @throws FileSystemException if an error occurs setting the decorator.
333     */
334    public void setFileObjectDecorator(final Class<?> fileObjectDecorator) throws FileSystemException
335    {
336        if (init)
337        {
338            throw new FileSystemException("vfs.impl/already-inited.error");
339        }
340        if (!DecoratedFileObject.class.isAssignableFrom(fileObjectDecorator))
341        {
342            throw new FileSystemException("vfs.impl/invalid-decorator.error", fileObjectDecorator.getName());
343        }
344
345        try
346        {
347            fileObjectDecoratorConst = fileObjectDecorator.getConstructor(new Class[]{FileObject.class});
348        }
349        catch (final NoSuchMethodException e)
350        {
351            throw new FileSystemException("vfs.impl/invalid-decorator.error", fileObjectDecorator.getName(), e);
352        }
353
354        this.fileObjectDecorator = fileObjectDecorator;
355    }
356
357    /**
358     * get the fileContentInfoFactory used to determine the infos of a file
359     * content.
360     * @return The FileContentInfoFactory.
361     */
362    @Override
363    public FileContentInfoFactory getFileContentInfoFactory()
364    {
365        return fileContentInfoFactory;
366    }
367
368    /**
369     * set the fileContentInfoFactory used to determine the infos of a file
370     * content.
371     * @param fileContentInfoFactory The FileContentInfoFactory.
372     * @throws FileSystemException if an error occurs setting the FileContentInfoFactory.
373     */
374    public void setFileContentInfoFactory(final FileContentInfoFactory fileContentInfoFactory)
375            throws FileSystemException
376    {
377        if (init)
378        {
379            throw new FileSystemException("vfs.impl/already-inited.error");
380        }
381
382        this.fileContentInfoFactory = fileContentInfoFactory;
383    }
384
385    /**
386     * Sets the file replicator to use. The manager takes care of all lifecycle
387     * management.
388     * @param replicator The FileReplicator.
389     * @throws FileSystemException if an error occurs setting the replicator.
390     */
391    public void setReplicator(final FileReplicator replicator)
392            throws FileSystemException
393    {
394        setupComponent(replicator);
395        fileReplicator = replicator;
396    }
397
398    /**
399     * Sets the temporary file store to use. The manager takes care of all
400     * lifecycle management.
401     * @param tempFileStore The temporary FileStore.
402     * @throws FileSystemException if an error occurs adding the file store.
403     */
404    public void setTemporaryFileStore(final TemporaryFileStore tempFileStore)
405            throws FileSystemException
406    {
407        setupComponent(tempFileStore);
408        this.tempFileStore = tempFileStore;
409    }
410
411    /**
412     * Sets the logger to use.
413     * @param log The Logger to use.
414     */
415    @Override
416    public void setLogger(final Log log)
417    {
418        this.log = log;
419    }
420
421    /**
422     * Initializes a component, if it has not already been initialised.
423     * @param component The component to setup.
424     * @throws FileSystemException if an error occurs.
425     */
426    private void setupComponent(final Object component)
427            throws FileSystemException
428    {
429        if (!components.contains(component))
430        {
431            if (component instanceof VfsComponent)
432            {
433                final VfsComponent vfsComponent = (VfsComponent) component;
434                vfsComponent.setLogger(getLogger());
435                vfsComponent.setContext(context);
436                vfsComponent.init();
437            }
438            components.add(component);
439        }
440    }
441
442    /**
443     * Closes a component, if it has not already been closed.
444     * @param component The component to close.
445     */
446    private void closeComponent(final Object component)
447    {
448        if (component != null && components.contains(component))
449        {
450            if (component instanceof VfsComponent)
451            {
452                final VfsComponent vfsComponent = (VfsComponent) component;
453                vfsComponent.close();
454            }
455            components.remove(component);
456        }
457    }
458
459    /**
460     * Returns the file replicator.
461     *
462     * @return The file replicator. Never returns null.
463     * @throws FileSystemException if there is no FileReplicator.
464     */
465    public FileReplicator getReplicator() throws FileSystemException
466    {
467        if (fileReplicator == null)
468        {
469            throw new FileSystemException("vfs.impl/no-replicator.error");
470        }
471        return fileReplicator;
472    }
473
474    /**
475     * Returns the temporary file store.
476     *
477     * @return The file store. Never returns null.
478     * @throws FileSystemException if there is no TemporaryFileStore.
479     */
480    public TemporaryFileStore getTemporaryFileStore()
481            throws FileSystemException
482    {
483        if (tempFileStore == null)
484        {
485            throw new FileSystemException("vfs.impl/no-temp-file-store.error");
486        }
487        return tempFileStore;
488    }
489
490    /**
491     * Initializes this manager.
492     *
493     * @throws FileSystemException if an error occurs during initialization.
494     */
495    public void init() throws FileSystemException
496    {
497        if (filesCache == null)
498        {
499            // filesCache = new DefaultFilesCache();
500            filesCache = new SoftRefFilesCache();
501        }
502        if (fileContentInfoFactory == null)
503        {
504            fileContentInfoFactory = new FileContentInfoFilenameFactory();
505        }
506
507        if (fileCacheStrategy == null)
508        {
509            fileCacheStrategy = CacheStrategy.ON_RESOLVE;
510        }
511
512        setupComponent(filesCache);
513        setupComponent(vfsProvider);
514
515        init = true;
516    }
517
518    /**
519     * Closes all files created by this manager, and cleans up any temporary
520     * files. Also closes all providers and the replicator.
521     */
522    public void close()
523    {
524        if (!init)
525        {
526            return;
527        }
528
529        // Close the providers.
530        for (final Object provider : providers.values())
531        {
532            closeComponent(provider);
533        }
534
535        // Close the other components
536        closeComponent(defaultProvider);
537        closeComponent(fileReplicator);
538        closeComponent(tempFileStore);
539
540        components.clear();
541        providers.clear();
542        filesCache.close();
543        localFileProvider = null;
544        defaultProvider = null;
545        fileReplicator = null;
546        tempFileStore = null;
547        init = false;
548    }
549
550    /**
551     * Free all resources used by unused filesystems created by this manager.
552     */
553    public void freeUnusedResources()
554    {
555        if (!init)
556        {
557            return;
558        }
559
560        // Close the providers.
561        for (final FileProvider fileProvider : providers.values())
562        {
563            final AbstractFileProvider provider = (AbstractFileProvider) fileProvider;
564            provider.freeUnusedResources();
565        }
566    }
567
568    /**
569     * Sets the base file to use when resolving relative URI.
570     * @param baseFile The new base FileObject.
571     * @throws FileSystemException if an error occurs.
572     */
573    public void setBaseFile(final FileObject baseFile)
574            throws FileSystemException
575    {
576        this.baseFile = baseFile;
577    }
578
579    /**
580     * Sets the base file to use when resolving relative URI.
581     * @param baseFile The new base FileObject.
582     * @throws FileSystemException if an error occurs.
583     */
584    public void setBaseFile(final File baseFile) throws FileSystemException
585    {
586        this.baseFile = getLocalFileProvider().findLocalFile(baseFile);
587    }
588
589    /**
590     * Returns the base file used to resolve relative URI.
591     * @return The FileObject that represents the base file.
592     * @throws FileSystemException if an error occurs.
593     */
594    @Override
595    public FileObject getBaseFile() throws FileSystemException
596    {
597        return baseFile;
598    }
599
600    /**
601     * Locates a file by URI.
602     * @param uri The URI of the file to locate.
603     * @return The FileObject for the located file.
604     * @throws FileSystemException if the file cannot be located or an error occurs.
605     */
606    @Override
607    public FileObject resolveFile(final String uri) throws FileSystemException
608    {
609        return resolveFile(getBaseFile(), uri);
610    }
611
612    /**
613     * Locate a file by URI, use the FileSystemOptions for file-system creation.
614     * @param uri The URI of the file to locate.
615     * @param fileSystemOptions The options for the FileSystem.
616     * @return The FileObject for the located file.
617     * @throws FileSystemException if the file cannot be located or an error occurs.
618     */
619
620    @Override
621    public FileObject resolveFile(final String uri,
622            final FileSystemOptions fileSystemOptions)
623            throws FileSystemException
624    {
625        // return resolveFile(baseFile, uri, fileSystemOptions);
626        return resolveFile(getBaseFile(), uri, fileSystemOptions);
627    }
628
629    /**
630     * Locates a file by URI.
631     * @param baseFile The base File to use to locate the file.
632     * @param uri The URI of the file to locate.
633     * @return The FileObject for the located file.
634     * @throws FileSystemException if the file cannot be located or an error occurs.
635     */
636    @Override
637    public FileObject resolveFile(final File baseFile, final String uri)
638            throws FileSystemException
639    {
640        final FileObject baseFileObj = getLocalFileProvider().findLocalFile(
641                baseFile);
642        return resolveFile(baseFileObj, uri);
643    }
644
645    /**
646     * Resolves a URI, relative to a base file.
647     * @param baseFile The base FileOjbect to use to locate the file.
648     * @param uri The URI of the file to locate.
649     * @return The FileObject for the located file.
650     * @throws FileSystemException if the file cannot be located or an error occurs.
651     */
652    @Override
653    public FileObject resolveFile(final FileObject baseFile, final String uri)
654            throws FileSystemException
655    {
656        return resolveFile(baseFile, uri, baseFile == null ? null : baseFile
657                .getFileSystem().getFileSystemOptions());
658    }
659
660    /**
661     * Resolves a URI, realtive to a base file with specified FileSystem
662     * configuration.
663     * @param baseFile The base file.
664     * @param uri The file name. May be a fully qualified or relative path or a url.
665     * @param fileSystemOptions Options to pass to the file system.
666     * @return A FileObject representing the target file.
667     * @throws FileSystemException if an error occurs accessing the file.
668     */
669    public FileObject resolveFile(final FileObject baseFile, final String uri,
670            final FileSystemOptions fileSystemOptions)
671            throws FileSystemException
672    {
673        final FileObject realBaseFile;
674        if (baseFile != null && VFS.isUriStyle()
675                && baseFile.getName().isFile())
676        {
677            realBaseFile = baseFile.getParent();
678        }
679        else
680        {
681            realBaseFile = baseFile;
682        }
683        // TODO: use resolveName and use this name to resolve the fileObject
684
685        UriParser.checkUriEncoding(uri);
686
687        if (uri == null)
688        {
689            throw new IllegalArgumentException();
690        }
691
692        // Extract the scheme
693        final String scheme = UriParser.extractScheme(uri);
694        if (scheme != null)
695        {
696            // An absolute URI - locate the provider
697            final FileProvider provider = providers.get(scheme);
698            if (provider != null)
699            {
700                return provider.findFile(realBaseFile, uri, fileSystemOptions);
701            }
702            // Otherwise, assume a local file
703        }
704
705        // Handle absolute file names
706        if (localFileProvider != null
707                && localFileProvider.isAbsoluteLocalName(uri))
708        {
709            return localFileProvider.findLocalFile(uri);
710        }
711
712        if (scheme != null)
713        {
714            // An unknown scheme - hand it to the default provider
715            if (defaultProvider == null)
716            {
717                throw new FileSystemException("vfs.impl/unknown-scheme.error", scheme, uri);
718            }
719            return defaultProvider.findFile(realBaseFile, uri, fileSystemOptions);
720        }
721
722        // Assume a relative name - use the supplied base file
723        if (realBaseFile == null)
724        {
725            throw new FileSystemException("vfs.impl/find-rel-file.error", uri);
726        }
727
728        return realBaseFile.resolveFile(uri);
729    }
730
731    /**
732     * Resolves a name, relative to the file. If the supplied name is an
733     * absolute path, then it is resolved relative to the root of the file
734     * system that the file belongs to. If a relative name is supplied, then it
735     * is resolved relative to this file name.
736     * @param root The base FileName.
737     * @param path The path to the file relative to the base FileName or an absolute path.
738     * @return The constructed FileName.
739     * @throws FileSystemException if an error occurs constructing the FileName.
740     */
741    @Override
742    public FileName resolveName(final FileName root, final String path)
743            throws FileSystemException
744    {
745        return resolveName(root, path, NameScope.FILE_SYSTEM);
746    }
747
748    /**
749     * Resolves a name, relative to the root.
750     *
751     * @param base the base filename
752     * @param name the name
753     * @param scope the {@link NameScope}
754     * @return The FileName of the file.
755     * @throws FileSystemException if an error occurs.
756     */
757    @Override
758    public FileName resolveName(final FileName base, final String name,
759            final NameScope scope) throws FileSystemException
760    {
761        final FileName realBase;
762        if (base != null && VFS.isUriStyle() && base.isFile())
763        {
764            realBase = base.getParent();
765        }
766        else
767        {
768            realBase = base;
769        }
770
771        final StringBuilder buffer = new StringBuilder(name);
772
773        // Adjust separators
774        UriParser.fixSeparators(buffer);
775        String scheme = UriParser.extractScheme(buffer.toString());
776
777        // Determine whether to prepend the base path
778        if (name.length() == 0 || (scheme == null && buffer.charAt(0) != FileName.SEPARATOR_CHAR))
779        {
780            // Supplied path is not absolute
781            if (!VFS.isUriStyle())
782            {
783                // when using uris the parent already do have the trailing "/"
784                buffer.insert(0, FileName.SEPARATOR_CHAR);
785            }
786            buffer.insert(0, realBase.getPath());
787        }
788
789        // // UriParser.canonicalizePath(buffer, 0, name.length());
790
791        // Normalise the path
792        final FileType fileType = UriParser.normalisePath(buffer);
793
794        // Check the name is ok
795        final String resolvedPath = buffer.toString();
796        if (!AbstractFileName
797                .checkName(realBase.getPath(), resolvedPath, scope))
798        {
799            throw new FileSystemException(
800                    "vfs.provider/invalid-descendent-name.error", name);
801        }
802
803        String fullPath;
804        if (scheme != null)
805        {
806            fullPath = resolvedPath;
807        }
808        else
809        {
810            scheme = realBase.getScheme();
811            fullPath = realBase.getRootURI() + resolvedPath;
812        }
813        final FileProvider provider = providers.get(scheme);
814        if (provider != null)
815        {
816            // todo: extend the filename parser to be able to parse
817            // only a pathname and take the missing informations from
818            // the base. Then we can get rid of the string operation.
819            // // String fullPath = base.getRootURI() +
820            // resolvedPath.substring(1);
821
822            return provider.parseUri(realBase, fullPath);
823        }
824
825        if (scheme != null)
826        {
827            // An unknown scheme - hand it to the default provider - if possible
828            if (defaultProvider != null)
829            {
830                return defaultProvider.parseUri(realBase, fullPath);
831            }
832        }
833
834        // todo: avoid fallback to this point
835        // this happens if we have a virtual filesystem (no provider for scheme)
836        return ((AbstractFileName) realBase).createName(resolvedPath, fileType);
837    }
838
839    /**
840     * Resolve the uri to a filename.
841     * @param uri The URI to resolve.
842     * @return The FileName of the file.
843     * @throws FileSystemException if an error occurs.
844     */
845    @Override
846    public FileName resolveURI(final String uri) throws FileSystemException
847    {
848        UriParser.checkUriEncoding(uri);
849
850        if (uri == null)
851        {
852            throw new IllegalArgumentException();
853        }
854
855        // Extract the scheme
856        final String scheme = UriParser.extractScheme(uri);
857        if (scheme != null)
858        {
859            // An absolute URI - locate the provider
860            final FileProvider provider = providers.get(scheme);
861            if (provider != null)
862            {
863                return provider.parseUri(null, uri);
864            }
865
866            // Otherwise, assume a local file
867        }
868
869        // Handle absolute file names
870        if (localFileProvider != null
871                && localFileProvider.isAbsoluteLocalName(uri))
872        {
873            return localFileProvider.parseUri(null, uri);
874        }
875
876        if (scheme != null)
877        {
878            // An unknown scheme - hand it to the default provider
879            if (defaultProvider == null)
880            {
881                throw new FileSystemException("vfs.impl/unknown-scheme.error", scheme, uri);
882            }
883            return defaultProvider.parseUri(null, uri);
884        }
885
886        // Assume a relative name - use the supplied base file
887        if (baseFile == null)
888        {
889            throw new FileSystemException("vfs.impl/find-rel-file.error", uri);
890        }
891
892        return resolveName(baseFile.getName(), uri, NameScope.FILE_SYSTEM);
893    }
894
895    /**
896     * Converts a local file into a {@link FileObject}.
897     * @param file The input File.
898     * @return the create FileObject
899     * @throws FileSystemException if an error occurs creating the file.
900     */
901    @Override
902    public FileObject toFileObject(final File file) throws FileSystemException
903    {
904        return getLocalFileProvider().findLocalFile(file);
905    }
906
907    /**
908     * Creates a layered file system.
909     * @param scheme The scheme to use.
910     * @param file The FileObject.
911     * @return The layered FileObject.
912     * @throws FileSystemException if an error occurs.
913     */
914    @Override
915    public FileObject createFileSystem(final String scheme,
916            final FileObject file) throws FileSystemException
917    {
918        final FileProvider provider = providers.get(scheme);
919        if (provider == null)
920        {
921            throw new FileSystemException("vfs.impl/unknown-provider.error", scheme, file);
922        }
923        return provider.createFileSystem(scheme, file, file.getFileSystem().getFileSystemOptions());
924    }
925
926    /**
927     * Creates a layered file system.
928     * @param file The FileObject to use.
929     * @return The layered FileObject.
930     * @throws FileSystemException if an error occurs.
931     */
932    @Override
933    public FileObject createFileSystem(final FileObject file)
934            throws FileSystemException
935    {
936        final String scheme = map.getScheme(file);
937        if (scheme == null)
938        {
939            throw new FileSystemException(
940                    "vfs.impl/no-provider-for-file.error", file);
941        }
942
943        return createFileSystem(scheme, file);
944    }
945
946    /**
947     * Determines if a layered file system can be created for a given file.
948     *
949     * @param file The file to check for.
950     * @return true if the FileSystem can be created.
951     * @throws FileSystemException if an error occurs.
952     */
953    @Override
954    public boolean canCreateFileSystem(final FileObject file)
955            throws FileSystemException
956    {
957        return map.getScheme(file) != null;
958    }
959
960    /**
961     * Creates a virtual file system.
962     * @param rootFile The FileObject to use.
963     * @return The FileObject in the VirtualFileSystem.
964     * @throws FileSystemException if an error occurs creating the file.
965     */
966    @Override
967    public FileObject createVirtualFileSystem(final FileObject rootFile)
968            throws FileSystemException
969    {
970        return vfsProvider.createFileSystem(rootFile);
971    }
972
973    /**
974     * Creates an empty virtual file system.
975     * @param rootUri The URI to use as the root of the FileSystem.
976     * @return A FileObject in the virtual FileSystem.
977     * @throws FileSystemException if an error occurs.
978     */
979    @Override
980    public FileObject createVirtualFileSystem(final String rootUri)
981            throws FileSystemException
982    {
983        return vfsProvider.createFileSystem(rootUri);
984    }
985
986    /**
987     * Locates the local file provider.
988     * @return The LocalFileProvider.
989     * @throws FileSystemException if an error occurs.
990     */
991    private LocalFileProvider getLocalFileProvider() throws FileSystemException
992    {
993        if (localFileProvider == null)
994        {
995            throw new FileSystemException(
996                    "vfs.impl/no-local-file-provider.error");
997        }
998        return localFileProvider;
999    }
1000
1001    /**
1002     * Get the URLStreamHandlerFactory.
1003     * @return The URLStreamHandlerFactory.
1004     */
1005    @Override
1006    public URLStreamHandlerFactory getURLStreamHandlerFactory()
1007    {
1008        return new VfsStreamHandlerFactory();
1009    }
1010
1011    /**
1012     * Closes the given filesystem.<br />
1013     * If you use VFS as singleton it is VERY dangerous to call this method
1014     * @param filesystem The FileSystem to close.
1015     */
1016    @Override
1017    public void closeFileSystem(final FileSystem filesystem)
1018    {
1019        // inform the cache ...
1020        getFilesCache().clear(filesystem);
1021
1022        // just in case the cache didnt call _closeFileSystem
1023        _closeFileSystem(filesystem);
1024    }
1025
1026    /**
1027     * Closes the given file system.
1028     * <p>
1029     * If you use VFS as singleton it is VERY dangerous to call this method
1030     * </p>
1031     *
1032     * @param filesystem The FileSystem to close.
1033     */
1034    public void _closeFileSystem(final FileSystem filesystem)
1035    {
1036        final FileProvider provider = providers.get(filesystem.getRootName().getScheme());
1037        if (provider != null)
1038        {
1039            ((AbstractFileProvider) provider).closeFileSystem(filesystem);
1040        }
1041    }
1042
1043    /**
1044     * This is an internal class because it needs access to the private member
1045     * providers.
1046     */
1047    final class VfsStreamHandlerFactory implements URLStreamHandlerFactory
1048    {
1049        @Override
1050        public URLStreamHandler createURLStreamHandler(final String protocol)
1051        {
1052            final FileProvider provider = providers.get(protocol);
1053            if (provider != null)
1054            {
1055                return new DefaultURLStreamHandler(context);
1056            }
1057
1058            // Route all other calls to the default URLStreamHandlerFactory
1059            return new URLStreamHandlerProxy();
1060        }
1061    }
1062
1063    /**
1064     * Get the schemes currently available.
1065     * @return The array of scheme names.
1066     */
1067    @Override
1068    public String[] getSchemes()
1069    {
1070        final String[] schemes = new String[providers.size()];
1071        providers.keySet().toArray(schemes);
1072        return schemes;
1073    }
1074
1075    /**
1076     * Get the capabilities for a given scheme.
1077     *
1078     * @param scheme The scheme to located.
1079     * @return A Collection of capabilities.
1080     * @throws FileSystemException if the given scheme is not konwn
1081     */
1082    @Override
1083    public Collection<Capability> getProviderCapabilities(final String scheme)
1084            throws FileSystemException
1085    {
1086        final FileProvider provider = providers.get(scheme);
1087        if (provider == null)
1088        {
1089            throw new FileSystemException("vfs.impl/unknown-scheme.error", scheme);
1090        }
1091
1092        return provider.getCapabilities();
1093    }
1094
1095    /**
1096     * Get the configuration builder for the given scheme.
1097     * @param scheme The scheme to locate.
1098     * @return The FileSystemConfigBuilder for the scheme.
1099     * @throws FileSystemException if the given scheme is not konwn
1100     */
1101    @Override
1102    public FileSystemConfigBuilder getFileSystemConfigBuilder(final String scheme)
1103            throws FileSystemException
1104    {
1105        final FileProvider provider = providers.get(scheme);
1106        if (provider == null)
1107        {
1108            throw new FileSystemException("vfs.impl/unknown-scheme.error", scheme);
1109        }
1110
1111        return provider.getConfigBuilder();
1112    }
1113
1114    // -- OPERATIONS --
1115
1116    /**
1117     * Adds the specified FileOperationProvider for the specified scheme.
1118     * Several FileOperationProvider's might be registered for the same scheme.
1119     * For example, for "file" scheme we can register SvnWsOperationProvider and
1120     * CvsOperationProvider.
1121     *
1122     * @param scheme The scheme the provider should be registered for.
1123     * @param operationProvider The FileOperationProvider.
1124     * @throws FileSystemException if an error occurs adding the provider.
1125     */
1126    @Override
1127    public void addOperationProvider(final String scheme,
1128            final FileOperationProvider operationProvider)
1129            throws FileSystemException
1130    {
1131        addOperationProvider(new String[] {scheme}, operationProvider);
1132    }
1133
1134    /**
1135     * @see FileSystemManager#addOperationProvider(String,
1136     *      org.apache.commons.vfs2.operations.FileOperationProvider)
1137     *
1138     * @param schemes The array of schemes the provider should apply to.
1139     * @param operationProvider The FileOperationProvider.
1140     * @throws FileSystemException if an error occurs.
1141     */
1142    @Override
1143    public void addOperationProvider(final String[] schemes,
1144            final FileOperationProvider operationProvider)
1145            throws FileSystemException
1146    {
1147        for (final String scheme : schemes)
1148        {
1149            if (!operationProviders.containsKey(scheme))
1150            {
1151                final List<FileOperationProvider> providers = new ArrayList<FileOperationProvider>();
1152                operationProviders.put(scheme, providers);
1153            }
1154
1155            final List<FileOperationProvider> providers = operationProviders.get(scheme);
1156
1157            if (providers.contains(operationProvider))
1158            {
1159                throw new FileSystemException(
1160                        "vfs.operation/operation-provider-already-added.error", scheme);
1161            }
1162
1163            setupComponent(operationProvider);
1164
1165            providers.add(operationProvider);
1166        }
1167    }
1168
1169    /**
1170     * @param scheme
1171     *            the scheme for wich we want to get the list af registered
1172     *            providers.
1173     *
1174     * @return the registered FileOperationProviders for the specified scheme.
1175     *         If there were no providers registered for the scheme, it returns
1176     *         null.
1177     *
1178     * @throws FileSystemException if an error occurs.
1179     */
1180    @Override
1181    public FileOperationProvider[] getOperationProviders(final String scheme)
1182            throws FileSystemException
1183    {
1184
1185        final List<?> providers = operationProviders.get(scheme);
1186        if (providers == null || providers.size() == 0)
1187        {
1188            return null;
1189        }
1190        return providers.toArray(new FileOperationProvider[] {});
1191    }
1192
1193    /**
1194     * Converts a URI into a {@link FileObject}.
1195     *
1196     * @param uri The URI to convert.
1197     * @return The {@link FileObject} that represents the URI.  Never
1198     *         returns null.
1199     * @throws FileSystemException On error converting the URI.
1200     * @since 2.1
1201     */
1202    @Override
1203    public FileObject resolveFile(final URI uri) throws FileSystemException
1204    {
1205        // TODO Push the URI deeper into VFS
1206        return resolveFile(baseFile, uri.toString(), null);
1207    }
1208
1209    /**
1210     * Converts a URL into a {@link FileObject}.
1211     *
1212     * @param url The URL to convert.
1213     * @return The {@link FileObject} that represents the URL.  Never
1214     *         returns null.
1215     * @throws FileSystemException On error converting the URL.
1216     * @since 2.1
1217     */
1218    @Override
1219    public FileObject resolveFile(final URL url) throws FileSystemException
1220    {
1221        try
1222        {
1223            return this.resolveFile(url.toURI());
1224        }
1225        catch (final URISyntaxException e)
1226        {
1227            throw new FileSystemException(e);
1228        }
1229    }
1230}